


iMacros.player = (function() {
    "use strict";
    const Cu = Components.utils;
    const Ci = Components.interfaces;
    const Cc = Components.classes;
    function require(url) {
        let scope = {};
        Cu.import(url, scope);
        return scope;
    }
    let {MiscUtils, StrUtils} = require("chrome://jsm/content/utils.js");
    let {Rijndael} = require("chrome://jsm/content/rijndael.js");
    let {Prefs} = require("chrome://jsm/content/Preferences.js");
    let {FIO} = require("chrome://jsm/content/FileHelpers.js");
    let {Storage} = require("chrome://jsm/content/Storage.js");
    let {pm} = require("chrome://jsm/content/passwordManager.js");
    let {RequestWatcher} = require("chrome://jsm/content/requestWatcher.js");
    let {Si} = require("chrome://jsm/content/ScriptingInterface.js");
    let {Dialogs} = require("chrome://jsm/content/Dialogs.js");
    let {NotImplemented, NotSupportedInE10s, BadParameter, UnsupportedCommand,
         RuntimeError, MacroSyntaxError, StopSignal} = 
        require("chrome://jsm/content/Exceptions.js");
    let {Clipboard} = require("chrome://jsm/content/Clipboard.js");
    let {ctypes} = require("resource://gre/modules/ctypes.jsm");
    let {Downloads} = require("resource://gre/modules/Downloads.jsm");
    let {Services} = require("resource://gre/modules/Services.jsm");
    let {OS} = require("resource://gre/modules/osfile.jsm");


    
    window.addEventListener("unload", function() {
        iMacros.player.onUnload();
    }, false);


    
    
    

    function MacroPlayer() {
        this.vars = new Array(10);
        this.userVars = new Map();
        this.timeouts = new Map();
        this.compileExpressions();
        this.bindActionHandlers();

        
        Services.obs.addObserver(this, "imacros-si-play", false);
        Services.obs.addObserver(this, "imacros-si-capture", false);
        Services.obs.addObserver(this, "imacros-si-show", false);

	
	
	const player_url = "chrome://imacros/content/frame_player.js";
	let mm = window.messageManager;
	mm.loadFrameScript(player_url, true);

        
        
        this._onTabSelect = this.onTabSelect.bind(this);
        this._onScriptError = this.onScriptError.bind(this);
    }

    MacroPlayer.prototype.onUnload = function() {
        Services.obs.removeObserver(iMacros.player, "imacros-si-play");
        Services.obs.removeObserver(iMacros.player, "imacros-si-capture");
        Services.obs.removeObserver(iMacros.player, "imacros-si-show");

	let mm = window.messageManager;
        const player_url = "chrome://imacros/content/frame_player.js";
	mm.removeDelayedFrameScript(player_url, true);

        
        if (this.imr_worker) {
            this.imr_worker.postMessage({"command": "terminate"});
        }
        
        if (iMacros.playing) {
            this.stop();
        }
    };

    
    MacroPlayer.prototype.ActionTable = new Object();
    MacroPlayer.prototype.RegExpTable = new Object();

    
    
    
    const im_strre = "(?:\"(?:[^\"\\\\]|\\\\[0btnvfr\"\'\\\\])*\"|"+
          "eval\\s*\\(\"(?:[^\"\\\\]|\\\\[\\w\"\'\\\\])*\"\\)|"+
          "\\S*)";
    

    function RepeatAction(interval) { this.interval = interval; }

    MacroPlayer.prototype.retry = function(onerror, msg, _timeout) {
        return new Promise((resolve, reject) => {
            
            let timeout = _timeout || 
                ((this.timeout_tag >= 0) ? this.timeout_tag :
                 this.timeout/10);
            if (!this.retryInterval) {
                this.retryStartTime = performance.now();
                this.retryInterval = setInterval(() => {
                    let remains = this.retryStartTime +
                        timeout*1000 - performance.now();
                    if (remains > 0) {
                        iMacros.panel.statLine2Status = "loading";
                        Services.obs.notifyObservers(
                            window, "imacros-delay-show",
                            msg+" "+(remains/1000).toFixed(1)+
                                "("+Math.round(timeout)+")s");
                    } else {
                        iMacros.panel.statLine2Status = "idle";
                        clearInterval(this.retryInterval);
                    }
                }, 200);
                return reject(new RepeatAction(this.retryInterval));
            } else {
                let elapsed_time = performance.now() - this.retryStartTime;
                if (elapsed_time > timeout*1000) {
                    iMacros.panel.statLine2Status = "idle";
                    clearInterval(this.retryInterval);
                    delete this.retryInterval;
                    if (this.ignoreErrors) 
                        return resolve();
                    
                    
                    
                    typeof onerror == "function" && onerror();
                    return reject(new RuntimeError("Retry timeout"));
                } else {
                    return reject(new RepeatAction(this.retryInterval));
                }
            }
        });
    };


    MacroPlayer.prototype.postMessage = function(topic, data, frame) {
        
        return new Promise((resolve, reject) => {
	    if (frame)
	        data.frame = frame;
	    let mm = this.currentBrowser.messageManager;
	    let response_handler = function(msg) {
                
                mm.removeMessageListener("imacros:"+topic+"-response",
				         response_handler);
	        resolve(msg.data);
	    };
	    mm.addMessageListener("imacros:"+topic+"-response",
			          response_handler);
	    mm.sendAsyncMessage("imacros:"+topic, data);
        });
    };


    

    
    
    MacroPlayer.prototype.RegExpTable["add"] =
        "^(\\S+)\\s+("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["add"] = function (cmd) {
        return new Promise((resolve, reject) => {
	    let param = StrUtils.unwrap(this.expandVariables(cmd[2]));
	    let m = null;
	    cmd[1] = cmd[1].toLowerCase();
	    if ( m = cmd[1].match(/^!var([0-9])$/) ) {
                let num = StrUtils.toInteger(m[1]);
                let n1 = StrUtils.toInteger(this.vars[num]);
                let n2 = StrUtils.toInteger(param);
                if ( !isNaN(n1) && !isNaN(n2) ) {
		    this.vars[num] = (n1 + n2).toString();
                } else {
		    this.vars[num] += param;
                }
	    } else if ( cmd[1] == '!extract') {
                this.addExtractData(param);
	    } else if (/^!\S+$/.test(cmd[1])) {
                throw new BadParameter("Unsupported variable "+
                                       cmd[1].toUpperCase()+
				       " for ADD command");
	    } else {
                if (!this.userVars.has(cmd[1])) {
		    throw new BadParameter("Undefinded vaiable "+
                                           cmd[1].toUpperCase());
                }
                let uv = this.userVars.get(cmd[1]);
                let n1 = StrUtils.toInteger(uv);
                let n2 = StrUtils.toInteger(param);
                if ( !isNaN(n1) && !isNaN(n2) ) {
		    this.userVars.set(cmd[1], (n1 + n2).toString());
                } else {
		    this.userVars.set(cmd[1], uv+param);
                }
	    }

            resolve("ADD");
        });
    };



    
    MacroPlayer.prototype.RegExpTable["back"] = "^\\s*$";

    MacroPlayer.prototype.ActionTable["back"] = function (cmd) {
        return new Promise((resolve, reject) => {
            getWebNavigation().goBack();
	    resolve("BACK");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["clear"] = "^\\s*$";

    MacroPlayer.prototype.ActionTable["clear"] = function (cmd) {
        
        
        return new Promise((resolve, reject) => {
            Services.cookies.removeAll();
            try {
                Services.cache2.clear();
            } catch(e) {
                Cu.reportError(e);
            }
	    resolve("CLEAR");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["click"] =
        "^x\\s*=\\s*(\\S+)\\s+y\\s*=\\s*(\\S+)"+
        "(?:\\s+content\\s*=\\s*("+im_strre+"))?\\s*$";
    
    MacroPlayer.prototype.ActionTable["click"] = function (cmd) {
        let data = {};
        data.x = StrUtils.toInteger(StrUtils.unwrap(
            this.expandVariables(cmd[1])
        ));
        data.y = StrUtils.toInteger(StrUtils.unwrap(
            this.expandVariables(cmd[2])
        ));
        if (isNaN(data.x))
            throw new BadParameter("positive integer number", 1);
        if (isNaN(data.y))
            throw new BadParameter("positive integer number", 2);
        data.cdata = cmd[3] ? this.parseContentStr(cmd[3]) : null;
        
        return this.postMessage("click-command", data, {number: 0})
            .then(rv => {
                if (rv.error)
                    throw new RuntimeError(rv.error.message, rv.error.errnum);
            });
    };


    
    MacroPlayer.prototype.RegExpTable["cmdline"] =
        "^(\\S+)\\s+("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["cmdline"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let param = StrUtils.unwrap(this.expandVariables(cmd[2]));
            if (/^!(\S+)$/i.test(cmd[1])) {
                let val = RegExp.$1.toLowerCase();
                if( val == "timeout" ) {
                    if (isNaN(StrUtils.toInteger(param)))
                        throw new BadParameter("integer", 2);
                    this.timeout = StrUtils.toInteger(param);
                } else if (val == "loop") {
                    if (isNaN(StrUtils.toInteger(param)))
                        throw new BadParameter("integer", 2);
                    this.currentLoop = StrUtils.toInteger(param);
                } else if (val == "datasource") {
                    return this.loadDataSource(param);
                } else if ( /^var([0-9])/.test(val) ) {
                    this.vars[StrUtils.toInteger(RegExp.$1)] = param;
                } else {
                    throw new BadParameter(
                        "!TIMEOUT|!LOOP|!DATASOURCE|!VAR[0-9]", 1
                    );
                }
            } else {
                if (this.userVars.has(cmd[1].toLowerCase())) {
                    this.userVars.set(cmd[1].toLowerCase(), param);
                } else {
                    throw new BadParameter("unknown variable "+cmd[1]);
                }
            }

	    resolve("CMDLINE");
        });
    };



    
    
    MacroPlayer.prototype.RegExpTable["disconnect"] = ".*";

    MacroPlayer.prototype.ActionTable["disconnect"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("DISCONNECT");
        });
    };



    
    
    MacroPlayer.prototype.RegExpTable["ds"] = ".*";

    MacroPlayer.prototype.ActionTable["ds"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("DS");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["event"] =
        "type\\s*=\\s*("+im_strre+")"+
        "(?:\\s+(selector|xpath)\\s*=\\s*("+im_strre+"))?"+
        "(?:\\s+(button|key|char|point)\\s*=\\s*("+im_strre+"))?"+
        "(?:\\s+modifiers\\s*=\\s*("+im_strre+"))?";

    MacroPlayer.prototype.formEventArgs = function(
        type, target_type, value_type, value, modifiers
    ) {
        let command = {
            type: type, button: 0, key: 0, char: "",
            point: null, modifiers: modifiers,
            highlight: Prefs.highlightElement
        };
        if (!value_type) {
            ; 
        } else if (value_type == "button") {
            command.button = StrUtils.toInteger(value);
            if (isNaN(command.button))
                throw new BadParameter("integer BUTTON value", 3);
        } else if (value_type == "key") {
            command.key = StrUtils.toInteger(value);
            if (isNaN(command.key))
                throw new BadParameter("integer KEY value", 3);
        } else if (value_type == "char") {
            command.char = target_type == "password" ?
                this.decryptString(value) : value;
        } else if (value_type == "point") {
            let point_re = /^\(\s*(\d+(?:\.\d+)?)\s*\,\s*(\d+(?:\.\d+)?)\s*\)$/;
            let m = null;
            if (!(m = point_re.exec(value)))
                throw new BadParameter("(x,y) POINT value", 3);
            command.point = {x: parseFloat(m[1]), y: parseFloat(m[2])};
        }

        return command;
    };

    MacroPlayer.prototype.ActionTable["event"] = function (cmd) {
        let type = StrUtils.unwrap(this.expandVariables(cmd[1])).toLowerCase();
        let selector_type = cmd[2] ? cmd[2].toLowerCase() : "";
        let selector = cmd[3] ?
            StrUtils.unwrap(this.expandVariables(cmd[3])) : "";
        let value_type = (cmd[4] || "").toLowerCase();
        let value = cmd[5] ?
            StrUtils.unwrap(this.expandVariables(cmd[5])) : 0;
        let modifiers = cmd[6] ?
            StrUtils.unwrap(this.expandVariables(cmd[6])) : "";
        let data = {type: selector_type, selector: selector};
        return this.postMessage("locate-element", data, this.currentFrame)
            .then(el => {
                
                
                
	        if (!el.found) {
	            return this.retry(() => {
                        throw new RuntimeError(
                            "Can not locate element specified by "+
                                selector_type+" \""+selector+"\"", 921
                        );
	            }, "Element waiting...");
	        }
                return this.formEventArgs(
                    type, el.type, value_type, value.trim(), modifiers
                );
            }).then(command => this.postMessage(
                "event-command", command, this.currentFrame
            ));
    };

    
    MacroPlayer.prototype.RegExpTable["events"] =
        "type\\s*=\\s*("+im_strre+")"+
        "(?:\\s+(selector|xpath)\\s*=\\s*("+im_strre+"))?"+
        "(?:\\s+(keys|chars|points)\\s*=\\s*("+im_strre+"))?"+
        "(?:\\s+modifiers\\s*=\\s*("+im_strre+"))?";

    MacroPlayer.prototype.formEventsArgs = function(
        type, target_type, value_type, value, modifiers
    ) {
        let command = {
            type: type,
            chars: "",
            keys: [],
            points: [],
            modifiers: modifiers,
            highlight: Prefs.highlightElement
        };
        let keys_re = /\[\d+(?:\s*,\s*\d+)*\]/;
        let points_re =
            /^(?:\s*\(\d+(?:\.\d+)?\s*\,\s*\d+(?:\.\d+)?\s*\)(?:\s*,\s*)?)+$/;
        if (value_type == "chars") {
            command.chars = target_type == "password" ?
                this.decryptString(value) : value;
        } else if (value_type.toLowerCase() == "keys") {
            if ( !keys_re.test(value) )
                throw new BadParameter("[k1,..,kn] as KEYS value", 3);
            command.keys = JSON.parse(value);
        } else if (value_type.toLowerCase() == "points") {
            if ( !points_re.test(value) )
                throw new BadParameter("(x,y)[,(x,y)] as POINTS value", 3);
            let point_re = /\(\s*(\d+(?:\.\d+)?)\s*\,\s*(\d+(?:\.\d+)?)\s*\)/g;
            while (m = point_re.exec(value)) {
                command.points.push({x: parseFloat(m[1]), y: parseFloat(m[2])});
            }
        }

        return command;
    };

    MacroPlayer.prototype.ActionTable["events"] = function (cmd) {
        let type = StrUtils.unwrap(this.expandVariables(cmd[1])).toLowerCase();
        let selector_type = cmd[2] ? cmd[2].toLowerCase() : "";
        let selector = cmd[3] ?
            StrUtils.unwrap(this.expandVariables(cmd[3])) : "";
        let value_type = (cmd[4] || "").toLowerCase();
        let value = cmd[5] ?
            StrUtils.unwrap(this.expandVariables(cmd[5])) : 0;
        let modifiers = cmd[6] ?
            StrUtils.unwrap(this.expandVariables(cmd[6])) : "";
        let data = {type: selector_type, selector: selector};
        return this.postMessage("locate-element", data, this.currentFrame)
            .then(el => {
                
                
                
	        if (!el.found) {
	            return this.retry(() => {
                        throw new RuntimeError(
                            "Can not locate element specified by "+
                                selector_type+" \""+selector+"\"", 921
                        );
	            }, "Element waiting...");
	        }
                return this.formEventsArgs(
                    type, el.type, value_type, value.trim(), modifiers
                );
            }).then(command => this.postMessage(
                "events-command", command, this.currentFrame
            ));
    };
    
    
    
    MacroPlayer.prototype.RegExpTable["extract"] = ".*";

    MacroPlayer.prototype.ActionTable["extract"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("EXTRACT");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["filedelete"] =
        "^name\\s*=\\s*("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["filedelete"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let path = OS.Path.normalize(
                StrUtils.unwrap(this.expandVariables(cmd[1]))
            );
            if (FIO.isFullPath(path))
                throw new BadParameter(
                    "absolute paths are prohibited for security reason", 1
                );
            let file_path = OS.Path.join(Prefs.defaultDownloadPath,
                                         ...OS.Path.split(path).components);
            OS.File.remove(file_path).then(() => resolve("FILEDELETE"), reject);
        });
    };


    
    MacroPlayer.prototype.RegExpTable["filter"] = "^type\\s*=\\s*(\\S+)\\s+"+
        "status\\s*=\\s*(\\S+)\\s*$";
    
    MacroPlayer.prototype.ActionTable["filter"] = function (cmd) {
        return new Promise((resolve, reject) => {
            var param = StrUtils.unwrap(this.expandVariables(cmd[2]));
            if (this.shouldFilterImages) {
                if (!/^images$/i.test(cmd[1])) {
                    throw new BadParameter("TYPE=IMAGES", 1);
                }
                if (param.toLowerCase() == "on")
                    RequestWatcher.enableImageFilter();
                else
                    RequestWatcher.disableImageFilter();
            }
	    resolve("FILTER");
        });
    };


    

    MacroPlayer.prototype.RegExpTable["frame"] =
        "^(f|name)\\s*=\\s*("+im_strre+")\\s*$";
    
    MacroPlayer.prototype.ActionTable["frame"] = function (cmd) {
        return Promise.resolve().then(() => {
	    let type = cmd[1].toLowerCase();
	    let param = StrUtils.unwrap(this.expandVariables(cmd[2]));
	    let frame_data = {};

	    if (type == "f") {
                param = StrUtils.toInteger(param);
                if (isNaN(param))
		    throw new BadParameter("F=<number>", 1);

                
                if (param == 0) {
		    this.currentFrame = {number: 0};
		    return Promise.resolve();
                }
	    }

	    if (type == "f")
                frame_data.number = param;
	    else if (type == "name")
                frame_data.name = param;
	    
	    return this.postMessage("frame-command", frame_data, {number: 0})
                .then((data) => {
                    if (!data.found) {
	            	return this.retry(() => {
		            throw new RuntimeError("Frame not found", 922);
	                }, "Frame waiting...");
	            } else {
                        if (type == "f")
                            this.currentFrame = {number: param};
                        else
                            this.currentFrame = {name: param};
                        return Promise.resolve();
	            }
                });
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["imageclick"] = ".*";
    MacroPlayer.prototype.ActionTable["imageclick"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("IMAGECLICK");
        });
    };

    
    MacroPlayer.prototype.RegExpTable["imagesearch"] =
	"^pos\\s*=\\s*("+im_strre+
	")\\s+image\\s*=\\s*("+im_strre+")\\s+"+
        "confidence\\s*=\\s*("+im_strre+")";
    
    MacroPlayer.prototype.getIMRLibPath = function() {
        if (!MiscUtils.isWindows())
            throw new Error("Image recognition is Windows-only feature.");

        
        let wrk = Cc["@mozilla.org/windows-registry-key;1"]
            .createInstance(Ci.nsIWindowsRegKey);
        try {
            wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
                     "SOFTWARE\\iOpus\\iMacros",
                     wrk.ACCESS_READ);
        } catch(e) {
            
            return "";
        }
        let path = wrk.readStringValue("PathExe");
        wrk.close();
        
        return OS.Path.join(path, "iimIRm.dll");
    };
    
    MacroPlayer.prototype.doImageSearch = function(img, tmpl, pos, cl) {
        return new Promise((resolve, reject) => {
            let onmessage = function(evt) {
                var msg = evt.data;
                if (msg.type == "message") {
                    
                    return;
                } else if (msg.type == "error") {
                    reject(new RuntimeError(msg.error, 903));
                    return;
                } 

                
                const TM_STATUS_MATCH_FOUND_OK  = 0;
                const TM_STATUS_MATCH_NOT_FOUND = 1;
                const TM_STATUS_FILE_IMAGE_NOT_FOUND = 2;
                const TM_STATUS_IMAGE_ILLEGAL_SIZE = 3;
                const TM_STATUS_INTERNAL_ERROR = 4;
                
                OS.File.remove(img);
                if (msg.rv == TM_STATUS_MATCH_FOUND_OK) {
                    
                    var data = {
                        x: msg.result.x,
                        y: msg.result.y,
                        width: msg.result.width,
                        height: msg.result.height
                    };
                    this.imageX = msg.result.x;
                    this.imageY = msg.result.y;
                    this.postMessage("highlight-image", data).then(resolve);
                } else if (msg.rv == TM_STATUS_MATCH_NOT_FOUND) {
                    this.retry(() => {
                        throw new RuntimeError("Image "+msg.template+
                                               " was not found", 927);
                    }, "Waiting for image...").then(resolve, reject);
	        } else if(msg.rv == TM_STATUS_FILE_IMAGE_NOT_FOUND) {
                    reject(new RuntimeError("Can not open image file "+
                                            msg.template, 930));
                } else {
                    reject(new RuntimeError("Image search error "+msg.rv, 903));
                }
            }.bind(this);

            let msg_no_free_beer = "This feature requires the iMacros image"+
                " recognition library, which is part of the commercial"+
                " iMacros Standard and Enterprise Editions.";

            if (!this.imr_worker) {
                let libpath = this.getIMRLibPath();
                if (!libpath) {
                    throw new RuntimeError(msg_no_free_beer, 902);
                }
                this.imr_worker = new ChromeWorker(
                    "chrome://imacros/content/imr_worker.js"
                );
                
                this.imr_worker.postMessage({
                    "command":  "init",
                    "libpath":  libpath
                });
            }

            this.imr_worker.onerror = reject;
            this.imr_worker.onmessage = onmessage;
            this.imr_worker.postMessage({
                "command":  "search",
                "image":  img,
                "template": tmpl,
                "pos": pos-1,  
                "confidenceLevel": cl
            });
        });
    };
    
    MacroPlayer.prototype.ActionTable["imagesearch"] = function (cmd) {
        return Promise.resolve().then(() => {
            if (!MiscUtils.isWindows()) {
                throw new UnsupportedCommand("IMAGESEARCH");
            }
            let pos = StrUtils.toInteger(
                StrUtils.unwrap(this.expandVariables(cmd[1]))
            );
            let image = StrUtils.unwrap(this.expandVariables(cmd[2]));
            let conf = StrUtils.toInteger(
                StrUtils.unwrap(this.expandVariables(cmd[3]))
            );
            if (!FIO.isFullPath(image)) {
                image = OS.Path.join(this.dataSourceFolder, 
                                     ...OS.Path.split(image).components);
            }
            
            let leaf_name = btoa(this.currentBrowser.currentURI.spec)+
                Date.now().toString()+".png";
            let tmp_file = OS.Path.join(OS.Constants.Path.tmpDir, leaf_name);
            return this.savePageAsImage(tmp_file, "png")
                .then(() => this.doImageSearch(tmp_file, image, pos, conf));
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["oncertificatedialog"] = ".*";
    MacroPlayer.prototype.ActionTable["oncertificatedialog"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("ONCERTIFICATEDIALOG");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["ondialog"] =
        "^pos\\s*=\\s*(\\S+)"+
        "\\s+button\\s*=\\s*(\\S+)"+
        "(?:\\s+content\\s*=\\s*("+im_strre+")?)?\\s*$";

    MacroPlayer.prototype.addDialogAction = function(action) {
        Services.obs.notifyObservers(window, "imacros-ondialog-action",
                                    JSON.stringify(action));
    };

    MacroPlayer.prototype.ActionTable["ondialog"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let pos = StrUtils.toInteger(
                StrUtils.unwrap(this.expandVariables(cmd[1]))
            );
            if (isNaN(pos) || pos < 1)
                throw new BadParameter("POS=<number>", 1);
            let button = StrUtils.unwrap(this.expandVariables(cmd[2]));
            let obj = new Object();
            obj.accept = /^(ok|yes)$/i.test(button);
            if (typeof cmd[3] != "undefined")
                obj.content = StrUtils.unwrap(this.expandVariables(cmd[3]));
            obj.timeout = this.delay;
            obj.pos = pos;
            this.addDialogAction(obj);
	    resolve("ONDIALOG");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["ondownload"] =
        "^folder\\s*=\\s*("+im_strre+")\\s+"+
        "file\\s*=\\s*("+im_strre+")"+
        "(?:\\s+wait\\s*=(yes|no|true|false))?"+
        "(?:\\s+checksum\\s*=(md5|sha1):(\\S+))?"+
        "\\s*$";

    MacroPlayer.prototype.ActionTable["ondownload"] = function (cmd) {
        let obj = new Object();
        let wait = true;
        let folder = StrUtils.unwrap(this.expandVariables(cmd[1]));
        let file = StrUtils.unwrap(this.expandVariables(cmd[2]));
        obj.accept = true;
        let folder_promise = null;
        
        if (folder == "*") {
            folder_promise = Promise.resolve(Prefs.defaultDownloadPath);
        } else {
            folder = FIO.isFullPath(folder) ? folder : 
                OS.Path.join(Prefs.defaultDownloadPath, 
                             ...OS.Path.split(folder).components);
            folder_promise = OS.File.exists(folder).then(exists => {
                if (exists)
                    return folder;
                else
                    throw new RuntimeError(
                        "can not open ONDOWNLOAD folder: '"+
                            folder+"'"+reason, 932);
            });
        }

        if (file != "*") {
            let re = MiscUtils.isWindows() ? /[\\\?\*\/\|\0]/ :
                /[\?\*\/\|\0]/;
            if (re.test(file) || /^\.\.?$/.test(file) ) {
                throw new BadParameter(
                    "file name contains illegal character(s)"
                );
            }
        }

        return folder_promise.then( dir => {
            obj.folder = dir;
            obj.filename = file;
            obj.timeout = this.delay;
            Storage.setSpecific(iMacros.wid, "onDownloadAction", obj);
            this.shouldDownloadPDF = true; 
            this.shouldWaitDownloadDlg = true;
            
            if (typeof cmd[3] != "undefined") {
                let param = StrUtils.unwrap(this.expandVariables(cmd[3]));
                wait = /^(?:yes|true)$/i.test(param);
            }
            this.shouldWaitDownload = wait;
            this.downloadFolder = dir;
            this.downloadFilename = file;
            if (typeof cmd[4] != "undefined") {
                if (!wait) {
                    throw new BadParameter("CHECKSUM requires WAIT=YES", 3);
                }
                this.downloadCheckAlg = 
                    StrUtils.unwrap(this.expandVariables(cmd[4]));
                this.downloadChecksum =
                    StrUtils.unwrap(this.expandVariables(cmd[5]))
                    .toLowerCase();
            } else {
                this.downloadChecksum = this.downloadCheckAlg = "";
            }
        });
    };

    MacroPlayer.prototype.handleDownloadDialog = function(src, tgt) {
        Promise.all(
            [Downloads.createDownload({source: src, target: tgt}),
             Downloads.getList(Downloads.PUBLIC)]
        ).then(([dl, list]) => {
            list.add(dl);
            if (this.shouldWaitDownload) {
                
                
                this.setTimeout("download", "Download...", () => {
                    dl.cancel();
                    throw new RuntimeError("Download timed out", 802)
                }, this.timeout);
            }
            this.clearTimeout("download_dlg");
            return dl.start().then(() => {
                if (!this.shouldWaitDownload)
                    return;
                this.clearTimeout("download");
                this.downloadedSize = dl.target.size;
                this.downloadedFilename = dl.target.path;
                if (this.downloadChecksum) {
                    this.calculateFileHash(
                        dl.target.path, this.downloadCheckAlg
                    ).then(check => {
                        if (check != this.downloadChecksum) {
                            
                            this.showErrorAndStop(
                                new RuntimeError(
                                    "Checksum of downloaded file "+check+
                                        " does not match specified", 934)
                            );
                            this.downloadChecksum = "";
                            this.downloadCheckAlg = "";
                        }
                    }).catch (e => this.showErrorAndStop(e));
                }
            }).catch(e => {
                if (this.shouldWaitDownload)
                    this.timeouts.get("downlaod").reject(e);
            }).then(() => dl.finalize(true));
        }).catch(Cu.reportError);
    };

    MacroPlayer.prototype.calculateFileHash = function (file, alg) {
        
        
        
        let ch = Cc["@mozilla.org/security/hash;1"]
            .createInstance(Ci.nsICryptoHash);
        
        let hash_alg = {"MD5": ch.MD5, "SHA1": ch.SHA1}[alg.toUpperCase()];
        if (!hash_alg)            
            throw new RuntimeError("Unknown Hash algorithm "+alg, 911);

        ch.init(hash_alg);

        
        
        return OS.File.read(file).then(data => {
            ch.update(data, data.length);
            let hash = ch.finish(false);
            let bytes = [];
            for(let x in hash)
                bytes.push(hash.charCodeAt(x));
            return bytes.reduce((s, c) => {
                return s + ("0" + c.toString(16)).slice(-2);
            }, "").toLowerCase();
        });
    };

    
    
    MacroPlayer.prototype.saveTarget = function(uri) {
	let filename = "", folder = null, ext = "", name = "";
        this.clearTimeout("download_dlg");
	if (/^data:/.test(uri)) {
	    
	    ext = "png";
	    name = "element_screenshot_"+Date.now();
	} else {
	    
	    
	    let m = null;
	    if ( m = uri.match(/\/([^\/?]+)(?=\?.+|$)/) ) {
		name = m[1];
		if (m = name.match(/\.([^\.\s]+)$/)) {
		    ext = m[1];
		    name = name.replace(/\.[^\.\s]+$/, "");
		}
	    }
	}

	if (!this.downloadFilename || this.downloadFilename == "*") {
	    filename = name+"."+ext;
	} else if (/^\+/.test(this.downloadFilename)) {
	    filename = name+this.downloadFilename.substring(1)+"."+ext;
	} else {
	    if (/\.[^\.\s]+$/i.test(this.downloadFilename)) {
		filename = this.downloadFilename
                    .replace(/\.[^\.\s]+$/,"."+ext);
	    } else {
		filename = this.downloadFilename;
            }
	}

	let file = (this.downloadFolder == "*" || !this.downloadFolder) ?
            Prefs.defaultDownloadPath : this.downloadFolder;
        
	file = OS.Path.join(file, filename);

	
        delete this.downloadFolder;
        delete this.downloadFilename;

        let wait = !!this.shouldWaitDownload;
        return Downloads.createDownload({source: uri, target: {path: file}})
            .then(dl => {
                dl.start();
                if (!wait)
                    return Promise.resolve();
                return dl.whenSucceeded().then(() => {
                    this.downloadedSize = dl.target.size;
                    this.downloadedFilename = dl.target.path;
                    return !this.downloadChecksum ?
                        Promise.resolve() :
                        this.calculateFileHash(file, this.downloadCheckAlg);
                }).then(checksum => {
                    if (!checksum)
                        return;
                    this.downloadChecksum = "";
                    this.downloadCheckAlg = "";
                    if (checksum != this.downloadChecksum)
                        throw new RuntimeError(
                            "Checksum of downloaded file "+check+
                                " does not match specified", 934);
                });
            }).catch(Cu.reportError);
    };



    

    MacroPlayer.prototype.RegExpTable["onerrordialog"] =
        "^(?:button\\s*=\\s*(?:\\S*))?\\s*(?:\\bcontinue\\s*=\\s*(\\S*))?\\s*$"

    MacroPlayer.prototype.onScriptError = function(msg) {
        if (this.shouldStopOnError) {
            msg.data.errnum = -992;
            this.showErrorAndStop(msg.data);
        }
            
    };

    MacroPlayer.prototype.ActionTable["onerrordialog"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let param = cmd[1] ?
                StrUtils.unwrap(this.expandVariables(cmd[1])) : "";
            
            if (/^no|false$/i.test(param)) {
                this.shouldStopOnError = true;
            }
	    resolve("ONERRORDIALOG");
        });
    };


    MacroPlayer.prototype.decryptString = function(cyphertext) {
        let plaintext = cyphertext;
        if (pm.encryptionType == pm.TYPE_NONE)
            return plaintext;
        let key = pm.getEncryptionKey();
        while(true) {
            try {
                plaintext = Rijndael.decryptString(cyphertext, key);
                break;
            } catch (e) {
                const reenter_msg = "Please re-enter your ecnryption password:";
                const check_msg = "Use this password as Master password";
                let param = {value: null};
                let check = {value: pm.encryptionType == pm.TYPE_STORED};
                let ok = Services.prompt.promptPassword(
                    window, "iMacros", reenter_msg,
                    param, check_msg, check
                );
                if (!ok) 
                    throw new RuntimeError(
                        "Aborted: User cancelled password dialog", -101
                    );
                key = param.value;
                if (check.value) {
                    pm.encryptionType = pm.TYPE_STORED;
                    pm.setMasterPwd(key);
                } else {
                    pm.encryptionType = pm.TYPE_TEMP;
                    pm.setSessionPwd(key);
                }
            }
        }

        return plaintext;
    };

    
    MacroPlayer.prototype.RegExpTable["onlogin"] =
        "^user\\s*=\\s*("+im_strre+")\\s+"+
        "password\\s*=\\s*("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["onlogin"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let username = StrUtils.unwrap(this.expandVariables(cmd[1]));
            let password = StrUtils.unwrap(this.expandVariables(cmd[2]));
            let obj = {
                accept: true,
                username: username,
                password: this.decryptString(password),
                timeout: this.delay
            };
            this.addDialogAction(obj);
	    resolve("ONLOGIN");
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["onprint"] = ".*";
    MacroPlayer.prototype.ActionTable["onprint"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("ONPRINT");
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["onsecuritydialog"] = ".*";
    MacroPlayer.prototype.ActionTable["onsecuritydialog"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("ONSECURITYDIALOG");
        });
    };



    
    
    MacroPlayer.prototype.RegExpTable["onwebpagedialog"] = ".*";
    MacroPlayer.prototype.ActionTable["onwebpagedialog"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("ONWEBPAGEDIALOG");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["pause"] = "^\\s*$";

    MacroPlayer.prototype.ActionTable["pause"] = function (cmd) {
        this.pause();
        iMacros.panel.updateControlPanel();
        return Promise.resolve();
    };


    
    
    MacroPlayer.prototype.RegExpTable["print"] = ".*";
    MacroPlayer.prototype.ActionTable["print"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("PRINT");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["prompt"] =
        "^("+im_strre+")"+
        "(?:\\s+("+im_strre+")"+
        "(?:\\s+("+im_strre+"))?)?\\s*$";

    MacroPlayer.prototype.ActionTable["prompt"] = function (cmd) {
        return Promise.resolve().then(() => {
	    
	    
	    
	    
            let text = StrUtils.unwrap(this.expandVariables(cmd[1]));
            let defval = cmd[3] ? 
                StrUtils.unwrap(this.expandVariables(cmd[3])) : "";

            if (typeof cmd[2] == "undefined") {
                Services.prompt.alert(window, "iMacros", text);
                return "PROMPT"
            }

            let title = "iMacros";
            let input = {value: defval};
            return Dialogs.prompt(title, text, input, null, {}).then(r => {
                if (typeof(r) == "undefined")
                    return;
                if (/!var([0-9])/i.test(cmd[2])) {
                    this.vars[StrUtils.toInteger(RegExp.$1)] = 
                        input.value;
                } else if (/[^!]\S*/.test(cmd[2])) {
                    this.userVars.set(cmd[2].toLowerCase(), input.value);
                }
            });
        });
    };


    
    MacroPlayer.prototype.RegExpTable["proxy"] =
        "^address\\s*=\\s*("+im_strre+")"+
        "(?:\\s+bypass\\s*=\\s*("+im_strre+")\\s*)?$";

    MacroPlayer.prototype.restoreProxySettings = function() {
        var pref = Services.prefs.getBranch("network.proxy.");
        pref.setCharPref("http", this.proxySettings.http);
        pref.setIntPref("http_port", this.proxySettings.http_port);
        pref.setCharPref("ssl", this.proxySettings.ssl);
        pref.setIntPref("ssl_port", this.proxySettings.ssl_port);
        pref.setCharPref("no_proxies_on", this.proxySettings.no_proxies_on);
        pref.setIntPref("type", this.proxySettings.type);
    };

    MacroPlayer.prototype.storeProxySettings = function() {
        var pref = Services.prefs.getBranch("network.proxy.");
        this.proxySettings = new Object();
        this.proxySettings.http = pref.getCharPref("http");
        this.proxySettings.http_port = pref.getIntPref("http_port");
        this.proxySettings.ssl = pref.getCharPref("ssl");
        this.proxySettings.ssl_port = pref.getIntPref("ssl_port");
        this.proxySettings.no_proxies_on = pref.getCharPref("no_proxies_on");
        this.proxySettings.type = pref.getIntPref("type");
    };

    
    

    MacroPlayer.prototype.ActionTable["proxy"] = function (cmd) {
        return new Promise((resolve, reject) => {
	    
	    
            let address = StrUtils.unwrap(this.expandVariables(cmd[1]));
            let bypass = cmd[2]? 
                StrUtils.unwrap(this.expandVariables(cmd[2])) : null;
            let pref = Services.prefs.getBranch("network.proxy.");

            
            if (address.toLowerCase() == "__default__") {
                pref.clearUserPref("http");
                pref.clearUserPref("http_port");
                pref.clearUserPref("ssl");
                pref.clearUserPref("ssl_port");
                pref.clearUserPref("no_proxies_on");
                pref.clearUserPref("type");
                return resolve("PROXY");
            } else if (address.toLowerCase() == "__none__") {
                pref.setIntPref("type", 0);
                return resolve("PROXY");
            }

            var addr_re = /^(?:(https?)\s*=\s*)?([\d\w\.]+):(\d+)\s*$/;
            var m = addr_re.exec(address);
            if (!m) {
                throw new BadParameter(
                    "server name or IP address with port number", 1
                );
            }

            if (!this.proxySettings) 
                this.storeProxySettings();

            var server = m[2];
            var port = StrUtils.toInteger(m[3]);

            if (!m[1]) {
                pref.setCharPref("http", server);
                pref.setIntPref("http_port", port);
                pref.setCharPref("ssl", server);
                pref.setIntPref("ssl_port", port);
            } else if (m[1].toLowerCase() == "http") {
                pref.setCharPref("http", server);
                pref.setIntPref("http_port", port);
            } else if (m[1].toLowerCase() == "https") {
                pref.setCharPref("ssl", server);
                pref.setIntPref("ssl_port", port);
            }

            if (bypass) {
                if (/^null$/i.test(bypass)) {
                    pref.setCharPref("no_proxies_on", "");
                } else {
                    pref.setCharPref("no_proxies_on",
                                     this.proxySettings.no_proxies_on+","+
                                     bypass);
                }
            }
            
            pref.setIntPref("type", 1);
	    return resolve("PROXY");
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["redial"] = ".*";
    MacroPlayer.prototype.ActionTable["redial"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("REDIAL");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["refresh"] = "^\\s*$";

    MacroPlayer.prototype.ActionTable["refresh"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let browser = this.currentBrowser;
            this.setTimeout("page_load", "Loading: ", () => {
                throw new RuntimeError(
                    "Page loading timeout, URL: "+
                        browser.contentURI.spec, 802
                );
            }, this.timeout);
            browser.reload();
            resolve();
        });
    };

    MacroPlayer.prototype.RegExpTable["stop"] = "^\\s*$";
    MacroPlayer.prototype.ActionTable["stop"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let browser = this.currentBrowser;
            this.setTimeout("page_load", "Loading: ", () => {
                throw new RuntimeError(
                    "Page loading timeout, URL: "+
                        browser.contentURI.spec, 802
                );
            }, this.timeout);
            browser.stop();
            resolve();
        });
    };

    MacroPlayer.prototype.deriveSaveAsName = function() {
	
	let uri = this.currentBrowser.currentURI;
	let name = uri.path;
	if (/\/([^\/]*)$/.test(name))
            name = RegExp.$1;
	
	if (!name.length) {
            if (/^(?:www\.)(\S+)/.test(uri.host))
		name = RegExp.$1;
	}
	
	if (!name.length)
            name = this.currentBrowser.contentTitle;
	
	if (!name.length)
            return "unknown";
	
	if (/^(.*)\.(?:\w+)$/.test(name))
            return RegExp.$1;

	return name;
    };


    
    MacroPlayer.prototype.RegExpTable["saveas"] =
        "^type\\s*=\\s*(\\S+)\\s+"+
        "folder\\s*=\\s*("+im_strre+")\\s+"+
        "file\\s*=\\s*("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["saveas"] = function (cmd) {
        let folder = StrUtils.unwrap(this.expandVariables(cmd[2]));
        let type = StrUtils.unwrap(this.expandVariables(cmd[1]))
            .toLowerCase();
        let folder_promise = null;
        
        if (folder == "*") {
            folder_promise = Promise.resolve(Prefs.defaultDownloadPath);
        } else {
            folder = FIO.isFullPath(folder) ? folder : 
                OS.Path.join(Prefs.defaultDownloadPath, 
                             ...OS.Path.split(folder).components);
            folder_promise = OS.File.exists(folder).then(exists => {
                if (exists)
                    return folder;
                else
                    throw new RuntimeError(
                        "can not open folder: '"+
                            folder+"'"+reason, 932);
            });
        }
        
        let filename = StrUtils.unwrap(this.expandVariables(cmd[3])), t;
        
        
        
        let re = new RegExp('\\s*[:*?|<>\\"/]+\\s*', "g");
        
        if (type == "extract") {
            if (filename == "*") {
                filename = "extract.csv";
            } else if (t = filename.match(/^\+(.+)$/)) {
                filename = "extract"+t[1]+".csv";
            }
            filename = filename.replace(re, "_");
            let data = this.getExtractData();
            if (/\[EXTRACT\]/.test(data)) {
                data = data.replace(/\"/g, '""');
                data = '"'+data.replace(/\[EXTRACT\]/g, '"'+
                                    this.dataSourceDelimiter+'"')+'"';
            }
            return folder_promise.then(folder => {
                let file = OS.Path.join(folder, filename);
                return FIO.appendTextFile(
                    file, data+(MiscUtils.isWindows() ? "\r\n": "\n")
                ).then(() => this.clearExtractData());
            });
        }

        if (filename == "*") {
            filename = this.deriveSaveAsName();
        } else if (t = filename.match(/^\+(.+)$/)) {
            filename = this.deriveSaveAsName() + t[1];
        }
        filename = filename.replace(re, "_");

        
        if (type == "cpl") {
            return Promise.reject(new NotSupportedInE10s("SAVEAS TYPE=CPL"));
        } else if (type == "htm" || type == "txt") {
            if (!/\.[^. ]+$/.test(filename))
                filename += "."+type;
            return folder_promise.then(folder => {
	        let f = OS.Path.join(folder, filename);
	        if (type == "htm") {
		    let uri = this.currentBrowser.currentURI.spec;
		    return Downloads.fetch(uri, f);
	        } else {
		    return this.postMessage("saveas-command", {}).then(s => {
		        return FIO.writeTextFile(f, s);
		    });
	        }
            });
        } else if (/^png|jpeg$/.test(type)) {
            return folder_promise.then(folder => {
	        if (!/\.[^. ]+$/.test(filename))
		    filename += "."+type;
	        let f = OS.Path.join(folder, filename);
                return this.savePageAsImage(f, type);
            });
        } else {
            return reject(new BadParameter(
                "iMacros for Firefox supports only "+
                    "CPL|HTM|TXT|EXTRACT|PNG|JPEG SAVEAS types"));
        }
        
    };


    MacroPlayer.prototype.takeBrowserScreenshot = function(
	filename, folder, type
    ) {
	
        
	return Promise.reject(
            new NotSupportedInE10s("SCREENSHOT TYPE=BROWSER")
        );
    };
    


    MacroPlayer.prototype.savePageAsImage = function(file, type) {
        let imageType = "";
        if (type == "jpeg") {
            imageType = "image/jpeg";
        } else if (type == "png") {
            imageType = "image/png";
        } else {
	    
            return Promise.reject(
                new RuntimeError("Unsupported image type "+type, 911)
            );
        }

	return this.postMessage("capture-page", {type: imageType}, {number: 0})
            .then((dataURL) => {
	        return Downloads.fetch(dataURL, file);
	    });
    };

    
    MacroPlayer.prototype.RegExpTable["screenshot"] =
        "^type\\s*=\\s*(browser|page)\\s+"+
        "(?:folder\\s*=\\s*("+im_strre+")\\s+)?"+
        "file\\s*=\\s*("+im_strre+")\\s*$";
    
    MacroPlayer.prototype.ActionTable["screenshot"] = function (cmd) {
        let type = cmd[1].toLowerCase();
        let folder = cmd[2] ?
            StrUtils.unwrap(this.expandVariables(cmd[2])) : null;
        
        let folder_promise = null;
        
        if (folder == "*" || !folder) {
            folder_promise = Promise.resolve(Prefs.defaultDownloadPath);
        } else {
            folder = FIO.isFullPath(folder) ? folder : 
                OS.Path.join(Prefs.defaultDownloadPath, 
                             ...OS.Path.split(folder).components);
            folder_promise = OS.File.exists(folder).then(exists => {
                if (exists)
                    return folder;
                else
                    throw new RuntimeError(
                        "can not open folder: '"+
                            folder+"'"+reason, 932);
            });
        }

	
        
        const re = new RegExp('\\s*[:*?|<>\\"/]+\\s*', "g");
	let filename = StrUtils.unwrap(this.expandVariables(cmd[3]));
        
        return folder_promise.then(folder => {
	    if (type == "browser") {
	        if (filename == "*") {
		    filename = "browser_screenshot";
	        }
                filename = filename.replace(re, "_");
	        return this.takeBrowserScreenshot(filename, folder, "png");
	    } else {
                if (filename == "*") {
		    filename = this.deriveSaveAsName();
                }
	        filename = filename.replace(re, "_");
	        let f = OS.Path.join(folder, filename);
	        return this.savePageAsImage(f, "png");;
	    }
        }).then(() => "SCREENSHOT");
    };


    
    MacroPlayer.prototype.RegExpTable["search"] =
        "^source\\s*=\\s*(txt|regexp):("+im_strre+")"+
        "(?:\\s+ignore_case\\s*=\\s*(yes|no))?"+
        "(?:\\s+extract\\s*=\\s*("+im_strre+"))?\\s*$";

    MacroPlayer.prototype.ActionTable["search"] = function (cmd) {
        return Promise.resolve().then(() => {
            let query = StrUtils.unwrap(this.expandVariables(cmd[2]));
            let extract = cmd[4] ? 
                StrUtils.unwrap(this.expandVariables(cmd[4])) : "";
            let ignore_case = cmd[3] && /^yes$/i.test(cmd[3]) ? "i" : "";
            
            
            if (extract && !(cmd[1].toLowerCase() == "regexp"))
                throw new BadParameter(
                    "EXTRACT has sense only for REGEXP search"
                );

	    let data = {
                type: cmd[1].toLowerCase(),
                query: query,
                extract: extract,
                ignore_case: ignore_case
	    };

	    return this.postMessage("search-command", data, this.currentFrame)
                .then(data => {
	            if (data.error) {
                        return Promise.reject(data.error);
	            } 

                    if (data.extract)
		        this.showAndAddExtractData(data.extract);

                    return Promise.resolve("SEARCH");
                });
        });
    };
    
    
    MacroPlayer.prototype.RegExpTable["set"] =
        "^(\\S+)\\s+("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["set"] = function (cmd) {
        return Promise.resolve().then(() => {
            let param = StrUtils.unwrap(this.expandVariables(cmd[2]));
            
            switch(cmd[1].toLowerCase()) {
            case "!encryption":
                let enc_types = {
                    "no": pm.TYPE_NONE,
                    "storedkey": pm.TYPE_STORED,
                    "tmpkey": pm.TYPE_TEMP
                };
                if (!(param.toLowerCase() in enc_types))
                    return Promise.reject(
                        new BadParameter("!ENCRYPTION can be only "+
                                         "YES|NO|STOREDKEY|TMPKEY")
                    );
                pm.encryptionType = enc_types[param.toLowerCase()];
                return "SET";
            case "!imagefilter":
                this.shouldFilterImages = param.toLowerCase() == "on"; 
                return "SET";
            case "!useragent":
                let branch = Services.prefs.getBranch("general.useragent.");
                if (!this.useragent) {
                    this.useragent = new Object();
                    if (branch.prefHasUserValue("override")) {
                        this.useragent.clear = false;
                        this.useragent.previousValue =
                            branch.getCharPref("override");
                    } else {
                        this.useragent.clear = true;
                    }
                }
                branch.setCharPref("override", param);
                return "SET";
            case "!loop":
                if (this.firstLoop) {
                    if (isNaN(StrUtils.toInteger(param)))
                        throw new BadParameter("!LOOP must be integer");
                    this.currentLoop = StrUtils.toInteger(param);
                    iMacros.panel.currentLoopValue = this.currentLoop;
                }
                return "SET";
            case "!extract":
                this.clearExtractData();
                if (!/^null$/i.test(param))
                    this.addExtractData(param);
                return "SET";
            case "!extractadd":
                this.addExtractData(param); 
                return "SET";
            case "!extract_test_popup":
                this.shouldPopupExtract = param.toLowerCase() == "yes";
                return "SET";
            case "!errorignore":
                this.ignoreErrors = param.toLowerCase() == "yes";
                return "SET";
            case "!filestopwatch":
                let filename = param, parent;
                if (FIO.isFullPath(filename)) {
                    parent = OS.Path.dirname(filename);
                } else {
                    parent = this.stopwatchFolder || Prefs.defaultDownloadPath;
                    parent = OS.Path.join(
                        parent, ...OS.Path.split(filename)
                            .components.slice(0, -1)
                    );
                }
                let file = OS.Path.join(
                    parent, OS.Path.split(filename).components.pop()
                );
                return OS.File.exists(parent).then(exists => {
                    if (!exists) {
                        throw new RuntimeError(
                            "Path "+parent+" does not exists", 932
                        );
                    }
                }).then(() => {
                    return FIO.appendTextFile(file, "").then(() => {
                        this.stopwatchFile = file;
                        this.shouldWriteStopwatchFile = true;
                    }).catch(e => {
                        throw new RuntimeError(
                            "can not write to STOPWATCH file: "+
                                file+", reason "+e.message, 931
                        );
                    }).then( () => "SET");
                });
            case "!folder_stopwatch":
                if (param.toLowerCase() == "no") {
                    this.shouldWriteStopwatchFile = false;
                    return "SET";
                } 
                return OS.File.exists(param).then(exists => {
                    if (exists)
                        this.stopwatchFolder = param;
                    else
                        throw new RuntimeError(
                            "can not open STOPWATCH folder: "+param, 931
                        );
                    this.shouldWriteStopwatchFile = true;
                });
            case "!stopwatch_header":
                if (param.toLowerCase() == "no") {
                    this.shouldWriteStopwatchHeader = false;
                } else if (param.toLowerCase() == "yes") {
                    this.shouldWriteStopwatchHeader = true;
                } else {
                    return Promise.reject(new BadParameter(
                        "!STOPWATCH_HEADER can be set to YES|NO only"
                    ));
                }
                return "SET";
            case "!folder_datasource":
                return OS.File.exists(param).then(exists => {
                    if (exists)
                        this.dataSourceFolder = param;
                    else
                        throw new RuntimeError(
                            "FOLDER_DATASOURCE: "+param+" does not exist", 931
                        );
                });
            case "!datasource":
                return this.loadDataSource(param); 
            case "!datasource_line":
                var x = StrUtils.toInteger(param);
                if (isNaN(x) || x <= 0)
                    return Promise.reject(new BadParameter(
                        "!DATASOURCE_LINE must be positive integer"
                    ));
                if (this.dataSource.length < x)
                    return Promise.reject(new RuntimeError(
                        "Invalid DATASOURCE_LINE value: "+param, 951
                    ));
                this.dataSourceLine = x;
                return "SET";
            case "!datasource_columns":
                if (isNaN(StrUtils.toInteger(param)))
                    return Promise.reject(new BadParameter(
                        "!DATASOURCE_COLUMNS must be integer"
                    ));
                this.dataSourceColumns = StrUtils.toInteger(param);
                return "SET";
            case "!datasource_delimiter":
                if (param.length > 1)
                    return Promise.reject(new BadParameter(
                        "!DATASOURCE_DELIMITER must be single character"
                    ));
                this.dataSourceDelimiter = param;
                return this.postMessage("set-datasource-delimiter",
                                        {delimiter: param}, {number: 0});
            case "!timeout": case "!timeout_page":
                var x = StrUtils.toInteger(param);
                if (isNaN(x) || x <= 0)
                    return Promise.reject(new BadParameter(
                        "!TIMEOUT must be positive integer"
                    ));
                this.timeout = x;
                this.timeout_tag = Math.round(this.timeout/10);
                return "SET";
            case "!timeout_macro":
                var x = parseFloat(param);
                if (isNaN(x) || x <= 0)
                    return Promise.reject(new BadParameter(
                        "!TIMEOUT_MACRO must be positive number"
                    ));
                this.globalTimer.setMacroTimeout(x);
                return "SET";
            case "!timeout_tag": case "!timeout_step":
                var x = StrUtils.toInteger(param);
                if (isNaN(x) || x < 0)
                    return Promise.reject(new BadParameter(
                        "!TIMEOUT_TAG must be positive integer"
                    ));
                this.timeout_tag = x;
                return "SET";
            case "!replayspeed":
                let delays = {"slow": 2000, "medium": 1000, "fast": 0};
                if (!delays.hasOwnProperty(param.toLowerCase()))
                    return Promise.reject(new BadParameter(
                        "!REPLAYSPEED can be only SLOW|MEDIUM|FAST"
                    ));
                this.delay = delays[param.toLowerCase()];
                return "SET";
            case "!singlestep":
                this.singleStepMode = param.toLowerCase() == "yes";
                return "SET";
            case "!clipboard":
                Clipboard.putString(param);
                return "SET";
            case "!linenumber_delta":
                var x = StrUtils.toInteger(param);
                if (isNaN(x) || x > 0)
                    return Promise.reject(new BadParameter(
                        "!LINENUMBER_DELTA must be negative integer or zero"
                    ));
                this.linenumber_delta = x;
                return "SET";
            case "!popup_allowed":
                let site = param.trim();
                let pmgr = Services.perms;
                try {
                    if (!/^[-\w]+:\/+/.test(site))
                        site = "http://"+site;
                    var uri = Services.io.newURI(site, null, null);
                } catch(e) {
                    return Promise.reject(new BadParameter(
                        "Wrong URL: "+param, 3
                    ));
                }
                if (this.popupAllowed) {
                    let popup = this.popupAllowed;
                    if (!popup.exists) {
                        pmgr.remove(popup.uri.host, "popup");
                    } else if (popup.blocked) {
                        pmgr.add(popup.uri, "popup", pmgr.DENY_ACTION);
                    }
                    this.popupAllowed = null;
                }

                let permission = pmgr.testPermission(uri, "popup");
                let exists = permission != pmgr.UNKNOWN_ACTION,
                    blocked = permission == pmgr.DENY_ACTION;
                if (!exists || blocked) {
                    pmgr.add(uri, "popup", pmgr.ALLOW_ACTION);
                    if (!exists)    
                        Prefs.prefBranch.setCharPref("popupAllowed", uri.host);
                }
                
                this.popupAllowed = 
                    {exists: exists, uri: uri, blocked: blocked};
                
                return "SET";

            case "!x_continue_load_after_stop":
                if (!/^(?:yes|no)$/i.test(param))
                    return Promise.reject(new BadParameter(
                        "!X_CONTINUE_LOAD_AFTER_STOP"+
                            " can be only YES|NO"
                    ));
                this.loadAfterStop = param.toLowerCase() == "yes";
                return "SET";

            case "!file_profiler":
                if (param.toLowerCase() == "no") {
                    this.writeProfilerData = false;
                    this.profiler.file = null;
                } else {
                    this.writeProfilerData = true;
                    this.profiler.enabled = true;
                    this.profiler.file = param;
                }
                return "SET";

            default:
                if (/^!var([0-9])$/i.test(cmd[1])) {
                    this.vars[StrUtils.toInteger(RegExp.$1)] = param;
                } else if (/^!\S+$/.test(cmd[1])) {
                    return Promise.reject(new BadParameter(
                        "Unsupported variable "+cmd[1]
                    ));
                } else {
                    this.userVars.set(cmd[1].toLowerCase(), param);
                }
            }

            return "SET";
        });
    };

    MacroPlayer.prototype.globalTimer = {
        init: function() {
            if (this.macroTimeout) {
                clearTimeout(this.macroTimeout);
                this.macroTimeout = null;
            }
            
            this.macroTimeoutValue = null;
        },

        start: function() {
            this.start_time = performance.now();
        },

        getElapsedTime: function() {
            if (!this.start_time)
                return 0;
            return (performance.now() - this.start_time)/1000;
        },

        setMacroTimeout: function(x) {
            if (this.macroTimeout) {
                clearTimeout(this.macroTimeout);
                this.macroTimeout = null;
            }
            this.macroTimeoutValue = x;
            this.macroTimeout = setTimeout( () => {
                iMacros.player.showErrorAndStop(
                    new RuntimeError(
                        "Max. macro runtime has been reached. Macro stopped.",
                        803)
                );
            }, Math.round(x*1000));
        },

        
        
        
        
        
        
        
        
        
        
        
        
        

        stop: function() {
            if (this.macroTimeout) {
                clearTimeout(this.macroTimeout);
                this.macroTimeout = null;
            }
            
            
            
            
        }
    };


    
    
    MacroPlayer.prototype.RegExpTable["size"] = ".*";
    MacroPlayer.prototype.ActionTable["size"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("SIZE");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["stopwatch"] =
        "^((?:(start|stop)\\s+)?id|label)\\s*=\\s*("+im_strre+")\\s*$";

    
    MacroPlayer.prototype.addTimeWatch = function(name) {
        this.stopwatchMap.set(name, this.globalTimer.getElapsedTime());
    };


    MacroPlayer.prototype.stopTimeWatch = function(name) {
        if (!this.stopwatchMap.has(name))
            throw new RuntimeError("time watch "+name+" does not exist", 962);
        let elapsed = this.globalTimer.getElapsedTime() - 
            this.stopwatchMap.get(name);
        this.lastWatchValue = elapsed;
        let x = {id: name, type: "id", elapsedTime: elapsed,
                 timestamp: new Date()};
        this.stopwatchResults.push(x);
    };


    MacroPlayer.prototype.addTimeWatchLabel = function(name) {
        let elapsed = this.globalTimer.getElapsedTime();
        this.lastWatchValue = elapsed;
        let x = {id: name, type: "label", elapsedTime: elapsed,
                 timestamp: new Date()};
        this.stopwatchResults.push(x);
    };


    
    MacroPlayer.prototype.ActionTable["stopwatch"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let action = cmd[2] ? cmd[2].toLowerCase() : null;
            let use_label = /label$/i.test(cmd[1]);
            let param = StrUtils.unwrap(this.expandVariables(cmd[3]));

            
            param = param.toUpperCase();
            
            if (use_label) {
                
                this.addTimeWatchLabel(param);
                resolve("STOPWATCH");
                return;
            }
            let found = this.stopwatchMap.has(param);
            if (action == "start") {
                if (found)
                    throw new RuntimeError("stopwatch id="+param+
                                           " already started", 961);
                this.addTimeWatch(param);
            } else if (action == "stop") {
                if (!found)
                    throw new RuntimeError("stopwatch id="+param+
                                           " wasn't started", 962);
                this.stopTimeWatch(param);
            } else {   
                found ? this.stopTimeWatch(param):
                    this.addTimeWatch(param);
            }
	    resolve("STOPWATCH");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["tab"] = "^(t\\s*=\\s*(\\S+)|"+
        "close|closeallothers|open|open\\s+new|new\\s+open"+
        ")\\s*$";

    MacroPlayer.prototype.ActionTable["tab"] = function (cmd) {
        return new Promise((resolve, reject) => {
            if (/^close$/i.test(cmd[1])) { 
                gBrowser.removeCurrentTab();
	        this.attachBrowser(gBrowser.selectedBrowser).then(resolve);
            } else if (/^closeallothers$/i.test(cmd[1])) {
                gBrowser.removeAllTabsBut(gBrowser.selectedTab);
                this.startTabIndex = 0;
	        resolve("TAB");
            } else if (/open/i.test(cmd[1])) {
                gBrowser.addTab();
	        resolve("TAB");
            } else if (/^t\s*=/i.test(cmd[1])) {
                let n = StrUtils.toInteger(this.expandVariables(cmd[2]));
                if (isNaN(n))
                    throw new BadParameter("T=<number>", 1);
                let tab_num = n+this.startTabIndex-1;
                let con = gBrowser.tabContainer;
                let tabs = con.childNodes;
                if (tab_num >= 0 && tab_num < tabs.length ) {
		    if (gBrowser.selectedTab != tabs[tab_num]) {
                        this.resolveTabSelectionPromise = resolve;
                        gBrowser.selectedTab = tabs[tab_num];
		    } else {
                        resolve("TAB");
                    }
                } else {
                    this.retry(function() {
                        throw new RuntimeError("Tab number "+n+
					       " does not exist", 971);
                    }, "Waiting for Tab...").then(resolve).catch(reject);
                }
            }
        });
    };

    MacroPlayer.prototype.onTabSelect = function(e) {
        this.detachBrowser().then(
            () => this.attachBrowser(gBrowser.selectedBrowser)
        ).then(() => {
            if (this.resolveTabSelectionPromise) {
                this.resolveTabSelectionPromise();
                delete this.resolveTabSelectionPromise;
            }
        }).catch(Cu.reportError);
    };

    

    
    const atts_re = "(?:[-\\w]+:"+im_strre+"(?:&&[-\\w]+:"+im_strre+")*|\\*?)";

    MacroPlayer.prototype.RegExpTable["tag"] =
        "^(?:pos\\s*=\\s*(\\S+)\\s+"+
        "type\\s*=\\s*(\\S+)"+
        "(?:\\s+form\\s*=\\s*("+atts_re+"))?\\s+"+
        "attr\\s*=\\s*("+atts_re+")"+
        "|xpath\\s*=\\s*("+im_strre+"))"+
        "(?:\\s+(content|extract)\\s*=\\s*"+
        "(\\d+(?::\\d+)*|"+                         
        "[%$]"+im_strre+"(?::[%$]"+im_strre+")*|"   
        +im_strre+"))?\\s*$";   

    MacroPlayer.prototype.parseContentStr = function(cs) {
	let rv = new Object();
	if (/^event:(\S+)$/i.test(cs)) {
            rv.type = "event";
            let known_types = new Set(
                ["saveitem", "savepictureas", "savetargetas", "savetarget", 
                 "mouseover", "fail_if_found", "save_element_screenshot"]
            );
            let etype = RegExp.$1.toLowerCase();
            if (known_types.has(etype)) {
		rv.etype = etype;
            } else{
		throw new RuntimeError("Unknown event type "+etype+
                                       " for tag command.", 911);
            }

            return rv;
	} 
        
        rv.type = "select";
        
        const val_re = new RegExp(
	    "^(?:([%$#])"+im_strre+")(?::\\1"+im_strre+")*$"
        );
        const idx_re = new RegExp("^\\d+(?::\\d+)*$");

        let m, split_re = null;
        
        if(m = cs.match(val_re)) {
	    let non_delimiter =
                "(?:\"(?:[^\"\\\\]|\\\\[0btnvfr\"\'\\\\])*\"|"+
                "eval\\s*\\(\"(?:[^\"\\\\]|\\\\[\\w\"\'\\\\])*\"\\)|"+
                "(?:[^:\\s]|:[^"+m[1]+"])+)";
	    split_re = new RegExp("(\\"+m[1]+non_delimiter+")", "g");
        } else if (m = cs.match(idx_re)) {
	    split_re = new RegExp("(\\d+)", "g");
        } else if (cs.toLowerCase() =="all") {
	    rv.seltype = "all";
	    return rv;
        } else {
	    
	    rv.type = "unknown";
	    return rv;
        }

        
        let g, opts = new Array();
        while(g = split_re.exec(cs)) {
	    opts.push(g[1]);
        }

        rv.seltype = opts.length > 1 ? "multiple" : "single";
        rv.opts = new Array();

        for (let opt of opts) {
	    if (/^([%$#])(.*)$/.test(opt)) {
                let typ = RegExp.$1;
                let val = RegExp.$2;
                val = StrUtils.unwrap(this.expandVariables(val));
                if (typ == "$" || typ == "%") {
		    let re_str = "^\\s*"+StrUtils.escapeREChars(val).
                        replace(/\*/g, '(?:[\r\n]|.)*')+"\\s*$";
		    rv.opts.push({typ: typ, re_str: re_str, str: val});
                } else if (typ == "#") {
		    let idx = StrUtils.toInteger(val);
		    if (isNaN(idx))
                        throw new RuntimeError(
			    "Wrong CONTENT specifier "+cs, 925);
		    rv.opts.push({typ: "#", idx: idx});
                }
	    } else if (/^(\d+)$/i.test(opt)) { 
                let idx = StrUtils.toInteger(RegExp.$1);
                if (isNaN(idx))
		    throw new RuntimeError("Wrong CONTENT specifier "+cs,
                                           925);
                rv.opts.push({typ: "#", idx: idx});
	    }
        }

	return rv;
    };


    MacroPlayer.prototype.ActionTable["tag"] = function (cmd) {
        return Promise.resolve().then(() => {
	    
	    let data = {
                pos: 0,
                relative: false,
                tagName: "",
                form: null,
                atts: null,
                xpath: null,
                type: "",
                txt: null,
                cdata: null,
                scroll: Prefs.scrollToElement,
                highlight: Prefs.highlightElement,
                download_pdf: this.shouldDownloadPDF
	    };

	    let a_password_element = false;
	    
	    if (cmd[5]) {       
                data.xpath = StrUtils.unwrap(this.expandVariables(cmd[5]));
	    } else {            
                data.pos = StrUtils.unwrap(this.expandVariables(cmd[1]));
                data.tagName = StrUtils.unwrap(this.expandVariables(cmd[2])).
		    toLowerCase();
                data.form = this.parseAtts(cmd[3]);
                data.atts = this.parseAtts(cmd[4]);
                data.atts_str = cmd[4]; 

                
                if (/^r(-?\d+)$/i.test(data.pos)) {
		    data.pos = StrUtils.toInteger(RegExp.$1);
		    data.relative = true;
                } else if (/^(\d+)$/.test(data.pos)) {
		    data.pos = StrUtils.toInteger(RegExp.$1);
		    data.relative = false;
                } else {
		    throw new BadParameter(
                        "POS=<number> or POS=R<number>"+
                            "where <number> is a non-zero integer", 1
                    );
                }
                
                if (/^(\S+):(\S+)$/i.test(data.tagName)) { 
		    if (!data.atts)
                        data.atts = new Object();
		    let val = RegExp.$2;
		    data.tagName = RegExp.$1.toLowerCase();
		    
		    a_password_element = val.toLowerCase() =="password";
		    val = StrUtils.escapeREChars(val);
		    val = val.replace(/\*/g, '(?:\n|.)*');
		    data.atts["type"] = "^"+val+"$";
                }

	    }

	    if (cmd[6]) {       
                data.type = cmd[6].toLowerCase();
                data.rawdata = cmd[7];
                data.txt = StrUtils.unwrap(this.expandVariables(cmd[7]));
                if (data.type == "content") 
		    data.cdata = this.parseContentStr(cmd[7]);
	    }

            
	    if (a_password_element && data.type == "content" && data.txt) {
	        data.txt = this.decryptString(data.txt);
	    }
	    
	    return this.postMessage("tag-command", data, this.currentFrame)
                .then(data => {
	            if (!data.found) {
	                return this.retry(() => {
		            if (data.extract){
                                this.showAndAddExtractData("#EANF#");
                            } else {
		                throw new RuntimeError(
                                    data.error.message, data.error.errnum
                                );
                            }
	                }, "Tag waiting...");
	            }
	            
	            if (data.error) {
                        throw data.error;
	            } else {
                        if (data.extract) {
		            this.showAndAddExtractData(data.extract);
		            return Promise.resolve();
	                } else if (data.targetURI) {
		            return this.saveTarget(data.targetURI);
	                } else {
		            return Promise.resolve();
	                }
	            }
	        });
        });
    };

    MacroPlayer.prototype.parseAtts = function(str) {
        if (!str || str == "*")
	    return null;
        let arr = str.split(new RegExp("&&(?=[-\\w]+:"+im_strre+")"));
        let parsed_atts = new Object(), at, val, m;
        const re = new RegExp("^([-\\w]+):("+im_strre+")$");
        for (let a of arr) {
	    if (!(m = re.exec(a)))
                throw new BadParameter("incorrect ATTR or FORM specifier: "+a);
	    at = m[1].toLowerCase();
	    if (at.length) {
                val = StrUtils.unwrap(this.expandVariables(m[2]));
                
                
                
                val = StrUtils.escapeTextContent(val);
                val = StrUtils.escapeREChars(val);
                val = val.replace(/\*/g, '(?:\n|.)*');
                
                val = val.replace(/ /g, "\\s+");
                parsed_atts[at] = "^\\s*"+val+"\\s*$";
	    } else {
                parsed_atts[at] = "^$";
	    }
        }

        return parsed_atts;
    };
    
    
    MacroPlayer.prototype.RegExpTable["url"] =
        "^goto\\s*=\\s*("+im_strre+")\\s*$";

    MacroPlayer.prototype.ActionTable["url"] = function (cmd) {
        return Promise.resolve().then(() => {
            let url = StrUtils.unwrap(this.expandVariables(cmd[1]));
            
            if (!/^([a-z]+):.*/i.test(url)) {
                url = "http://"+url;
            } 
            
            try {
                
                Services.io.newURI(url, null, null);
            } catch (e) {
                throw new BadParameter("The URL syntax is not correct: '"+
                                       url+"'");
            }

            try {
                
                
                
                

                
                
                
                

                
                gBrowser.loadURIWithFlags(
                    url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
                )
                
                
                
                
	        this.requestURL = url;
	        this.networkErrorProxyConnection = false;
                this.networkErrorHTTPCode = 0;
                this.setTimeout("page_load", "Loading: ", () => {
                    throw new RuntimeError(
                        "Page loading timeout, URL: "+url, 802
                    );
                }, this.timeout);
            } catch (e) {
                
                var s = e.toString();
                
                if (/NS_ERROR_FILE_NOT_FOUND/.test(s))
                    throw new RuntimeError("File "+url+" not found", 930);
                else
                    throw e;
            }

	    return Promise.resolve();
        }).then(() => {this.currentFrame = {number: 0};});
    };

    MacroPlayer.prototype.RegExpTable["js_eval"] =
        "^code\\s*=\\s*("+im_strre+")\\s*$";
    MacroPlayer.prototype.ActionTable["js_eval"] = function (cmd) {
        return Promise.resolve().then(() => {
            let mycode = StrUtils.unwrap(this.expandVariables(cmd[1]));
            try {
                /* 这里的alert,window 是IM插件的window对象,和网站的页面没关系
                alert(mycode);
                let iRet=window.eval(mycode);
                alert(iRet);
                */
               /* 这里获取到的还是IM插件的window对象
               let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
                // Get the list of browser windows already open
                let d_windows = wm.getEnumerator("navigator:browser");
                while (d_windows.hasMoreElements()) {
                    try {
                        let domWindow = d_windows.getNext().QueryInterface(Ci.nsIDOMWindow);
                        domWindow.alert(mycode);
                        let iRet=domWindow.eval(mycode);
                        alert(iRet);
                    }catch(ee){}
                }
               */
              /*
              得用document.getElementById('content').selectedBrowser.contentDocument
              这个才可以访问到我们需要的网站页面的Documnet
              */
             document.getElementById('content').selectedBrowser.contentWindow.eval(mycode);
            } catch (e) {
                    throw e;
            }

	    return Promise.resolve();
        }).then(() => {this.currentFrame = {number: 0};});
    };

    
    MacroPlayer.prototype.RegExpTable["version"] = "^(?:build\\s*=\\s*(\\S+))?"+
        "(?:\\s+recorder\\s*=\\s*(\\S+))?\\s*$";
    MacroPlayer.prototype.ActionTable["version"] = function (cmd) {
        return new Promise((resolve, reject) => {
            resolve("VERSION");
        });
    };


    
    MacroPlayer.prototype.RegExpTable["wait"] = "^seconds\\s*=\\s*(\\S+)\\s*$";

    MacroPlayer.prototype.ActionTable["wait"] = function (cmd) {
        return new Promise((resolve, reject) => {
            let param = Number(StrUtils.unwrap(this.expandVariables(cmd[1])));
            if (isNaN(param))
                throw new BadParameter("SECONDS=<number>", 1);
            
            if ((param = Math.round(param*10)*100) < 0) 
                throw new BadParameter("positive number of seconds", 1);

            let start = performance.now();
            this.waitTimeout = setTimeout(() => {
                if (this.waitInterval) {
                    clearInterval(this.waitInterval);
                    delete this.waitInterval;
                }
                resolve("WAIT");
            }, param);

            let period = 200; 
            this.waitInterval = setInterval(() => {
                if (!this.waitCommandSuspended) {
                    let remains = performance.now() - start;
                    iMacros.panel.statLine1 = "Waiting: "+
                        (remains/1000).toFixed(2).toString()+
                        " ("+param/1000+")s";
                    if (!this.playing) {
                        clearTimeout(this.waitTimeout);
                        delete this.waitTimeout;
                        clearInterval(this.waitInterval);
                        delete this.waitInterval;
                        return;
                    }
                    if (this.paused) {
                        iMacros.panel.statLine1 = "";
                        clearTimeout(this.waitTimeout);
                        delete this.waitTimeout;
                        this.waitCommandSuspended = true;
                        this.waitCommandRemains = Math.round(remains);
                    }
                } else {
                    
                    
                    
                    
                    if (!this.paused) {
                        this.waitTimeout = setTimeout(() => {
                            if (this.waitInterval) {
                                clearInterval(this.waitInterval);
                                delete this.waitInterval;
                            }
                            resolve("WAIT");
                        }, this.waitCommandRemains);
                        delete this.waitCommandSuspended;
                        delete this.waitCommandRemains;
                    }
                }
            }, period);
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["winclick"] = ".*";
    MacroPlayer.prototype.ActionTable["winclick"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("WINCLICK");
        });
    };


    
    
    MacroPlayer.prototype.RegExpTable["saveitem"] = ".*";
    MacroPlayer.prototype.ActionTable["saveitem"] = function (cmd) {
        return new Promise((resolve, reject) => {
            throw new UnsupportedCommand("SAVEITEM");
        });
    };


    

    MacroPlayer.prototype.compileExpressions = function () {
        for (let x in this.RegExpTable) {
            try {
                this.RegExpTable[x] = new RegExp(this.RegExpTable[x], "i");
            } catch (e) {
                console.log("failed on compiling regexp "+x);
                throw e;
            }
        }
    };

    MacroPlayer.prototype.bindActionHandlers = function() {
        for (let x in this.ActionTable) {
            this.ActionTable[x] = this.ActionTable[x].bind(this);
        }
    };
    
    MacroPlayer.prototype.handleSICommand = function(subject, topic, data) {
        if (subject != window)
            return;
        if (topic == "imacros-si-play") {
            let play_args = JSON.parse(data);
            let macro = {};
            if (play_args.type == "source")
                macro.source = play_args.source;
            else
                macro.path = FIO.isFullPath(play_args.filePath) ? 
                play_args.filePath : FIO.getMacroPath(play_args.filePath);

            
            this.profiler.si_enabled = play_args.use_profiler;

            for (let x in play_args.vars)
                this.userVars.set(x.toLowerCase(), play_args.vars[x]);
            
            if (/\.js$/.test(play_args.filePath)) {
                iMacros.client_id = play_args.clientId;
                iMacros.playjs(macro);
            } else {
                iMacros.player.client_id = play_args.clientId;
                iMacros.player.play(macro);
            }
            iMacros.panel.updateControlPanel();

        } else if (topic == "imacros-si-capture") {
            let capture_args = JSON.parse(data);
            let file_path = capture_args.filePath;
            let type = capture_args.type;
	    if (type == "browser") {
		this.takeBrowserScreenshot(
		    OS.Path.basename(file_path), 
                    OS.Path.dirname(file.parent), "png").then( () => {
			Si.sendResponse(capture_args.clientId, "OK", 1);
		    }).catch(
                        e => Si.sendResponse(
                            capture_args.clientId, e.message, e.errnum
                        )
                    );
	    } else {
		this.savePageAsImage(file_path, "png").then(() => {
		    Si.sendResponse(capture_args.clientId, "OK", 1);
		}).catch(
                    e => Si.sendResponse(
                        capture_args.clientId, e.message, e.errnum
                    )
                );
	    }
        } else if (topic == "imacros-si-show") {
            let show_args = JSON.parse(data);
            iMacros.panel.showInfoMessage(show_args.message);
            Si.sendResponse(show_args.clientId, "OK", 1);
        }
    };


    MacroPlayer.prototype.observe = function (subject, topic, data) {
        if (/^imacros-si-/.test(topic)) {
            this.handleSICommand(subject, topic, data);
        }
    };
    
    
    
    MacroPlayer.prototype.attachBrowser = function(browser) {
        return Promise.resolve().then(() => {
	    this.currentBrowser = browser;
            let mm = this.currentBrowser.messageManager;
	    mm.addMessageListener("imacros:error-occurred",
			          this._onScriptError);
	    return this.postMessage(
                "enable-player",
                {time: this.globalTimer.getElapsedTime()}
            );
        });
    };

    MacroPlayer.prototype.detachBrowser = function() {
	return Promise.resolve().then(() => {
            let mm = this.currentBrowser.messageManager;
	    mm.removeMessageListener("imacros:error-occurred",
			             this._onScriptError);
	    return this.postMessage("disable-player", {});
        }).then(() => { this.currentBrowser = null; });
    };


    
    
    MacroPlayer.prototype.onOpenWindow = function(xulwin) {
        
        let url = "chrome://mozapps/content/downloads/unknownContentType.xul";
        
        let win = xulwin.QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIDOMWindow);
        
        win.addEventListener("load", function listener() {
            win.removeEventListener("load", listener, false);
            if (win.document.URL == url) {
                Services.scriptloader.loadSubScript(
                    "chrome://imacros/content/downloadHook.js", win
                );
            }
        });
    };

    MacroPlayer.prototype.onCloseWindow = function(xulwin) {};
    MacroPlayer.prototype.onWindowTitleChange = function(xulwin, title) {};

    MacroPlayer.prototype.registerObservers = function () {
        Services.wm.addListener(this);
        gBrowser.tabContainer.addEventListener(
            "TabSelect", this._onTabSelect, false
        );
        gBrowser.addProgressListener(this);
    };


    MacroPlayer.prototype.unregisterObservers = function () {
        Services.wm.removeListener(this);
        gBrowser.tabContainer.removeEventListener(
            "TabSelect", this._onTabSelect, false
        );
        gBrowser.removeProgressListener(this);
    };
    
    MacroPlayer.prototype.setTimeout = function(type, msg, ontimeout, timeout) {
        
        
        
        
        if (this.timeouts.has(type))
            return;
        let x = new Object();
        x.start = performance.now();
        x.promise = new Promise((resolve, reject) => {
            x.timeout = setTimeout(() => {
                try {
                    typeof ontimeout == "function" && ontimeout();
                    
                    
                    console.assert(typeof ontimeout != "function",
                                   "Timeout reached");
                    resolve();
                } catch(e) {
                    reject(e);
                } finally {
                    clearTimeout(x.timeout);
                    clearInterval(x.interval);
                    this.timeouts.delete(type);
                    iMacros.panel.statLine2Status = "idle";
                }
            }, Math.round(timeout*1000));
            x.resolve = resolve;
            x.reject = reject;
        });

        x.interval = setInterval(() => {
            let elapsed_time = ((performance.now() - x.start)/1000)
                .toFixed(1).toString();
	    Services.obs.notifyObservers(
                window, "imacros-delay-show",
		msg+elapsed_time+'('+timeout.toFixed(1).toString()+')s'
            );
        }, 500);
        this.timeouts.set(type, x);
        iMacros.panel.statLine2Status = "loading";

        return x;
    };

    MacroPlayer.prototype.clearTimeout = function(type) {
        if (!this.timeouts.has(type))
            return;
        let x = this.timeouts.get(type);
        x.resolve();
        clearTimeout(x.timeout);
        clearInterval(x.interval);
        this.timeouts.delete(type);
        iMacros.panel.statLine2Status = "idle";
    };
    
    MacroPlayer.prototype.forceTimeout = function(type, error) {
        console.assert(this.timeouts.has(type), 
                       "Trying to force non-existent timeout!");
        if (!this.timeouts.has(type))
            return;
        let x = this.timeouts.get(type);
        x.reject(error);
        clearTimeout(x.timeout);
        clearInterval(x.interval);
        this.timeouts.delete(type);
        iMacros.panel.statLine2Status = "idle";
    };

    
    MacroPlayer.prototype.onStateChange = function(progress, req, flag, stat) {
	
    	console.assert(
            this.playing, "WebProgressListener hasn't been detached!"
        );

        let url = null;
        
        if (!(flag & Ci.nsIWebProgressListener.STATE_IS_NETWORK))
            return;     
        
        if(flag & Ci.nsIWebProgressListener.STATE_START) {
            try {
                if (!this.timeouts.has("page_load")) {
                    if (req) {
                        let url = req.QueryInterface(Ci.nsIChannel).
                            originalURI.spec;
                        
                        
                        
                        if (! /^(?:https?|file):\/\//.test(url))
                            return;
                        this.requestURL = url;
                    }
                    this.networkErrorProxyConnection = false;
                    this.networkErrorHTTPCode = 0;
                    this.setTimeout("page_load", "Loading: ", () => {
                        throw new RuntimeError(
                            "Page loading timeout, URL: "+url, 802
                        );
                    }, this.timeout);
                }
            } catch (e) {
                Cu.reportError(e);
            }
        } else if(flag & Ci.nsIWebProgressListener.STATE_STOP) {
            if (!this.timeouts.has("page_load"))
                return;
            
            
            
            
            
            
    	    
    	    
    	    const NS_IMAGELIB_ERROR_LOAD_ABORTED = 0x80540008;
    	    const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
    	    const NS_BINDING_ABORTED = 0x804B0002;
            this.networkError = !(Components.isSuccessCode(stat) ||
                                  stat == NS_IMAGELIB_ERROR_LOAD_ABORTED ||
                                  stat == NS_ERROR_PARSED_DATA_CACHED ||
                                  stat == NS_BINDING_ABORTED);
	    
            if (this.networkError) {
                
    		
                
                
                
                
                
                if (stat == Cr.NS_ERROR_PROXY_CONNECTION_REFUSED)
                    this.networkErrorProxyConnection = true;
            } else {
                
                try {
                    var x = req.QueryInterface(Ci.nsIHttpChannel);
                    
                    
                    
                    
                    
                    
                    if (!x.requestSucceeded ) {
                        this.networkError = true;
                        this.networkErrorHTTPCode = x.responseStatus;
                    }
                } catch(e) {
                    
                    
                    
                }
            }

            if (this.networkError) {
                this.networkError = false;
                let msg = "Error loading page "+this.requestURL;
                let code = -1001;
                if (this.networkErrorProxyConnection) {
                    code = -935;
                    msg += " (Proxy server refused connection)";
                } else {
                    code = -933;
                    if (this.networkErrorHTTPCode) {
                        msg += " (HTTP status code "+
                            this.networkErrorHTTPCode +")";
                    }
                }
                this.forceTimeout("page_load", new RuntimeError(msg, code));
            } else {
                this.clearTimeout("page_load");
            }
        }
    };

    MacroPlayer.prototype.QueryInterface = function(iid) {
        if (iid.equals(Ci.nsIWebProgressListener) ||
            iid.equals(Ci.nsISupportsWeakReference) ||
            iid.equals(Ci.nsISupports))
            return this;
        throw Components.results.NS_NOINTERFACE;
    };


    MacroPlayer.prototype.checkAFCompatibility = function(line, num, iDrone) {
        const forbiddenCommands = new RegExp(
            "^(?:pause|prompt|clear|ds|size|imageclick|imagesearch|print)$",
            "i");

        const forbiddenVariables = new RegExp(
            "^(?:timeout_macro|clipboard|filestopwatch|file_profiler|"+
                "folder_datasource|folder_stopwatch|loop|singlestep|datasource)$",
            "i");


        if (/^\s*(\w+)(?:\s+(.*))?$/.test(line)) {
            var command = RegExp.$1.toLowerCase();
            if (forbiddenCommands.test(command)) {
                throw new Error(
                    "Command "+command+" on line "+num+
                        " is not compatible with Alertfox"
                );
            }
        }

        if (/^\s*set\s+!(\w+)/i.test(line)) {
            var variable = RegExp.$1.toLowerCase();
            if (forbiddenVariables.test(variable)) {
                throw new Error(
                    "Variable !"+variable+
                        " on line "+num+" is not compatible with Alertfox"
                );
            } else if (variable == "encryption") {
                if (!/!encryption\s+no\s*$/i.test(line) && !iDrone) {
                    throw new Error(
                        "Only iDrone allows encrypted passwords"
                    );
                }
            }
        }
    };


    
    MacroPlayer.prototype.parseMacro = function(lines, warnOnLoop, AFTest) {
        return new Promise((resolve, reject) => {
            const comment = new RegExp("^\\s*(?:'.*)?$");
            const linenumber_delta_re =
                  new RegExp("^\\s*'\\s*!linenumber_delta\\s*:\\s*(-?\\d+)",
                             "i");

            
	    this.actions = new Array();

            let line_count = 0;
            let too_long_msg = "Macro length exceeds "+
                Prefs.maxMacroLength+
                " lines, this might take too long to load.\n\n"+
                "Would you like to proceed?\n\n"+
                "Note: You can disable this warning message by "+
                "increasing the"+
                "\n\"extensions.imacros.maxMacroLength\" parameter.";

            for (let line of lines) {
                
                
                line_count++;
                
                if (line_count > Prefs.maxMacroLength) {
                    let conf = Services.prompt.confirm(window, "iMacros", 
                                                       too_long_msg);
                    if (!conf) {
                        throw new RuntimeError("Macro stopped manually", -101);
                    }
                }
                
                
                let m = line.match(linenumber_delta_re);
                if (m) {
                    this.linenumber_delta = StrUtils.toInteger(m[1]);
                    continue;
                }
                
                
                if (line.match(comment))
                    continue;

                if (AFTest) {
                    
                    
                    this.checkAFCompatibility(line, line_count, AFTest.iDrone);
                }
                if ( warnOnLoop && /\{\{\!loop\}\}/i.test(line) &&
                     !Prefs.noWarningOnLoop) {
                    warnOnLoop = false;
                    let title = "iMacros";
                    let msg = "You started a macro that contains LOOP "+
                        "functions with the normal PLAY button. "+
                        "This means the macro will run only once. "+
                        "The value of the {{!LOOP}} variable is 1.\n\n"+
                        "You need to start the macro with the LOOP button"+
                        " (not the PLAY button) to run it in loops."
                    let check_msg = "Display this message next time";
                    let check = {value: true};
                    Services.prompt.alertCheck(
                        MiscUtils.browserWindow, title, msg, check_msg, check
                    );
                    Prefs.noWarningOnLoop = !check.value;
                }
                
                if (/^\s*(\w+)(?:\s+(.*))?$/.test(line.trim())) {
                    let command = RegExp.$1.toLowerCase();
                    
                    if (!(command in this.RegExpTable))
                        throw new MacroSyntaxError(
                            "unknown command: "+command.toUpperCase()+
                                ", line "+(line_count+this.linenumber_delta)
                        );
                    
                    
                    let args = this.RegExpTable[command].exec(RegExp.$2 || "");
                    if ( !args )
                        throw new MacroSyntaxError(
                            "wrong format of "+command.toUpperCase()+" command"+
                                ", line "+(line_count+this.linenumber_delta)
                        );
                    
                    
                    this.actions.push({name: command,
                                       args: args, line: line_count});
                    
                } else {
                    throw new MacroSyntaxError(
                        "can not parse macro line "+
                            (line_count+this.linenumber_delta)+": "+line
                    );
                }
            }
            
            if (AFTest) {
                let af_actions = new Array();
                af_actions.push({name: "clear", args: [], line: 0});
                af_actions.push({name: "set", args: [
                    "", "!TIMEOUT_MACRO", "300"
                ], line: 0});
                af_actions.push({name: "tab", args: [
                    "", "T=", "1"
                ], line: 0});
                af_actions.push({name: "tab", args: [
                    "", "CLOSEALLOTHERS"
                ], line: 0});
                af_actions.push({name: "set", args: [
                    "", "!LINENUMBER_DELTA", "-5"
                ], line: 0});
                this.actions = af_actions.concat(this.actions);
            }

            resolve();
        });
    };
    
    MacroPlayer.prototype.actionListIterator = function *() {
        do {
            this.action_stack = this.actions.slice();
            this.action_stack.reverse();
            this.beforeEachRun();
            while (this.action_stack.length)
                yield this.action_stack.pop();

            yield {
                afterRunAction: true, 
                afterRunPromise: this.afterEachRun()
            };
            this.firstLoop = false;
            this.currentLoop++;
            if (this.times > 1)
                iMacros.panel.currentLoopValue = this.currentLoop;
        } while(this.currentLoop <= this.times);
    };

    function __string_to_line_iterator(s) {
        let line = "";
        for(let i = 0; i < s.length; i++) {
            if (s[i] == '\r')
                continue;
            if (s[i] != '\n') {
                line += s[i];
            } else {
                yield line;
                line = "";
            }
        }
        yield line;
    };

    MacroPlayer.prototype.initialize = function *(macro, AFTest) {
        if (macro.path) {
            let leaf_name = OS.Path.basename(macro.path);
            let exists = yield OS.File.exists(macro.path);
            if (!exists)
                throw new RuntimeError(
                    "Macro "+leaf_name+" was not found", 930
                );
            this.source = yield FIO.readTextFile(macro.path);
            if (!this.source.length) 
                throw new RuntimeError(
                    "File "+leaf_name+
                        " is empty or can not be read", 931
                );
            let name = macro.name || leaf_name;
            this.currentMacro = {name: name, path: macro.path};
        } else if (macro.source) {
            this.source = macro.source;
            let name = macro.name || "Embedded macro";
            this.currentMacro = {name: name, path: null};
        }
        
        this.cycledReplay = (this.times = macro.times || 1) > 1;
        let warn_on_loop = !(this.cycledReplay || 
                             iMacros.in_iimPlay ||
                             this.client_id);
        yield this.parseMacro(__string_to_line_iterator(this.source), 
                              warn_on_loop, AFTest);
        
        iMacros.panel.statLine3 = name;
        iMacros.panel.showLines(this.source);
        iMacros.panel.updateControlPanel();
        yield this.registerObservers();
        yield this.attachBrowser(gBrowser.selectedBrowser);
        this.globalTimer.start();
    };

    MacroPlayer.prototype.perform = function *() {
        for (let action of this.actionListIterator()) {
            if (action.afterRunAction) {
                yield action.afterRunPromise;
                continue;
            }
            this.currentAction = action; 
            
            try {
                iMacros.panel.highlightLine(action.line);
                this.profiler.start(action);
                yield Promise.race([this.ActionTable[action.name](action.args),
                                    this.stopPromise]);
                
                
                
                if (this.retryInterval) {
                    clearInterval(this.retryInterval);
                    delete this.retryInterval;
                }
                
                
                
                if (this.timeouts.has("download_dlg"))
                    yield Promise.race([
                        this.timeouts.get("download_dlg").promise,
                        this.stopPromise
                    ]);
                
                
                let timeout_promises =
                    Array.from(this.timeouts.values(), t => t.promise);
                yield Promise.race([
                    Promise.all(timeout_promises), this.stopPromise
                ]);
                
                
                
                
                if (this.shouldWaitDownloadDlg) {
                    this.shouldWaitDownloadDlg = false;
                    this.setTimeout("download_dlg", "", () => {
                        let e_msg = "ONDOWNLOAD command was used but"+
                            " no download occurred.";
                        throw new RuntimeError(e_msg, 804);
                    }, this.timeout_tag);
                }
                this.profiler.end("OK", 1, this);
            } catch(e if e instanceof RepeatAction) {
                console.log("e of RepeatAction");
                this.action_stack.push(this.currentAction);
            } catch(e) {
                yield this.handleError(e);
            }
            
            if (this.singleStepMode) {
                yield this.pause();
                yield this.pausePromise;
            } else if (this.paused) {
                yield this.pausePromise;
            } else {
                
                yield Promise.race([new Promise(
                    (resolve, reject) => setTimeout(resolve, this.delay)
                ), this.stopPromise]);
            }
        }
    };

    MacroPlayer.prototype.notifyStateChange = function(state) {
        Services.obs.notifyObservers(window, "imacros-state-change", state);
    };

    MacroPlayer.prototype.play = function(macro, AFTest) {
        
        this.reset();
        this.playing = true;
        this.notifyStateChange("playing");
        return Task.spawn(this.initialize(macro, AFTest))
            .then(() => Task.spawn(this.perform()))
            .then(() => Task.spawn(this.terminate()))
            .catch( e => {
                if (e instanceof StopSignal) {
                    
                    return Task.spawn(this.terminate());
                } else {
                    
                    iMacros.panel.showErrorMessage(e, e.errnum || -1001);
                }
            }).then(() => this.cleanup()).catch(Cu.reportError);
    };

    
    MacroPlayer.prototype.showErrorAndStop = function(e) {
        iMacros.panel.statLine2Status = "idle";
        this.errorMessage = e.message;
        this.errorCode = e.errnum ? e.errnum : -1001;
	if (this.currentAction) {
            this.errorMessage += ", line: "+
		(this.currentAction.line+this.linenumber_delta).toString();
	}
        iMacros.panel.showErrorMessage(
            this.errorMessage, this.errorCode
        );
        this.rejectStopPromise(e);
    };

    MacroPlayer.prototype.handleError = function (e) {
	console.error(e);
	this.errorMessage = e.message;
        this.errorCode = e.errnum ? e.errnum : -1001;
	if (this.currentAction) {
	    this.errorMessage += ", line: "+
		(this.currentAction.line+this.linenumber_delta).toString();
	}
	this.profiler.end(this.errorMessage, this.errorCode, this);
	if (this.playing && !this.ignoreErrors) {
            iMacros.panel.showErrorMessage(
                this.errorMessage, this.errorCode
            );
	    return this.saveStopwatchResults().then(() => {
	        return Promise.reject(new StopSignal());
            });
	}
    };

    MacroPlayer.prototype.stop = function(err) {
        let e = err || new RuntimeError("Aborted: User pressed Stop button"+
                                        " in iMacros sidebar.", -101);
        if (this.paused)
            this.resolvePausePromise();
        this.rejectStopPromise(e);
    };

    MacroPlayer.prototype.terminate = function *() { 
        try {
            this.resolveStopPromise(); 
            yield this.unregisterObservers();
            yield this.detachBrowser();
            yield this.writeProfilerData ? 
                this.saveProfilerData() : Promise.resolve();
            
            for (let type of this.timeouts.keys())
                this.clearTimeout(type);
            if (this.retryInterval) {
                clearInterval(this.retryInterval);
                delete this.retryInterval;
            }
        } catch (e) {
            Cu.reportError(e);
        }
    };

    MacroPlayer.prototype.cleanUpProxySettings = function() {
        if (this.proxySettings) {
            this.restoreProxySettings();
            this.proxySettings = null;
        }
    };

    MacroPlayer.prototype.cleanUpUserAgent = function() {
        if (this.useragent) {
            var branch = Services.prefs.getBranch("general.useragent.");
            if (this.useragent.clear) {
                branch.clearUserPref("override");
            } else {
                branch.setCharPref("override", this.useragent.previousValue);
            }
            this.useragent = null;
        }
    };


    MacroPlayer.prototype.cleanUpPopupAllowed = function() {
        
        if (this.popupAllowed) {
            var popup = this.popupAllowed;
            if (!popup.exists) {
                Services.perms.remove(popup.uri.host, "popup");
            } else if (popup.blocked) {
                Services.perms.add(popup.uri, "popup", 
                                   Services.perms.DENY_ACTION);
            }
            this.popupAllowed = null;
            Prefs.prefBranch.clearUserPref("popupAllowed");
        }
    };
    

    MacroPlayer.prototype.cleanup = function() {
        
        this.vars = new Array(10);
        this.userVars.clear();
        this.cleanUpPopupAllowed();
        this.cleanUpUserAgent();
        this.cleanUpProxySettings();

        
        iMacros.panel.currentLoopValue = 1;

        
        if (!this.loadAfterStop) {
            getWebNavigation().stop(Ci.nsIWebNavigation.STOP_ALL);
        }
        
        
        
        var branch = Services.prefs.getBranch("browser.link.");
        if (Storage.has("open_newwindow")) {
            branch.setIntPref("open_newwindow", 
                              Storage.get("open_newwindow"));
            Storage.clear("open_newwindow");
        }

        if (Storage.has("open_newwindow_restriction")) {
            branch.setIntPref(
                "open_newwindow.restriction",
                Storage.get("open_newwindow_restriction")
            );
            Storage.clear("open_newwindow_restriction");
        }
        
        
        this.playing = false;
        this.notifyStateChange("idle");
        if (!iMacros.in_iimPlay) {
            
            
            iMacros.panel.showMacroTree();
            if (this.client_id) {   
                var extra = {
                    extractData: this.getExtractData(),
                    lastPerformance: this.lastPerformanceArray,
                };
                if (this.profiler.si_enabled) {
                    delete this.profiler.si_enabled;
                    extra.profilerData =
                        this.profiler.getResultingXMLFragment(this);
                }
                Si.sendResponse(
                    this.client_id,
                    this.errorMessage,
                    this.errorCode,
                    extra
                );
                Si.releaseClient(this.client_id);
                delete this.client_id;
            }
        }
        
    };

    
    MacroPlayer.prototype.saveStopwatchResults = function() {
        return Promise.resolve().then(() => {
            
            this.globalTimer.stop();

            
            this.totalRuntime = this.globalTimer.getElapsedTime();
            

            
            let format = function(x) {
                var m = x.toFixed(3).match(/^(\d+)\.(\d{3})/);
                var s = m[1];
                while (s.length < 5)
                    s = "0"+s;
                
                return s+"."+m[2];
            };
            
            this.lastPerformance = "Total Runtime="+
                format(this.totalRuntime)+"[!S!]";

            if (!this.lastPerformanceArray)
                this.lastPerformanceArray = new Array();

            this.lastPerformanceArray.push(
                {
                    name: "TotalRuntime",
                    value: this.totalRuntime.toFixed(3).toString()
                }
            );
            
            const delim = this.dataSourceDelimiter;
            let newline = MiscUtils.isWindows() ? "\r\n" : "\n";
            let s = "";
            if (this.shouldWriteStopwatchHeader) {
                
                let now = new Date();
                let d = StrUtils.formatDate("yyyy/mm/dd", now);
                let t = StrUtils.formatDate("hh:nn", now);
                s = "\"Date: "+d+"  Time: "+t+
                    ", Macro: "+iMacros.currentMacro.name+
                    ", Status: "+this.errorMessage+" ("+this.errorCode+")\""+
                    delim+delim+delim;
                s += newline;
            }

            if (!this.stopwatchResults || !this.stopwatchResults.length)
                return;

            for (var i = 0; i < this.stopwatchResults.length; i++) {
                
                let r = this.stopwatchResults[i];
                let timestamp = StrUtils.formatDate(
                    "yyyy/mm/dd"+delim+"hh:nn:ss", r.timestamp
                );
                s += timestamp+delim+r.id+delim+
                    r.elapsedTime.toFixed(3).toString();
                s += newline;
                this.lastPerformance += r.id+"="+
                    format(r.elapsedTime)+"[!S!]";
                this.lastPerformanceArray.push(
                    {
                        name: r.id,
                        value: r.elapsedTime.toFixed(3).toString()
                    }
                );
            }

            
            if (!this.shouldWriteStopwatchFile)
                return;

            let file = null;

            if (this.stopwatchFile) {
                file = this.stopwatchFile;
            } else {
                let folder = this.stopwatchFolder || Prefs.defaultDownloadPath;
                let filename = /^(.+)\.iim$/.test(this.currentMacro.name) ?
                    RegExp.$1 : this.currentMacro.name;
                file = OS.Path.join(folder, "performance_"+filename+".csv");
            }

            return FIO.appendTextFile(file, s).catch(e => {
                this.errorCode = -931;
                this.errorMessage = "RuntimeError: "+
                    "Can not write to file "+file;
                iMacros.panel.showErrorMessage(
                    this.errorMessage, this.errorCode
                );
            });
        });
    };


    MacroPlayer.prototype.pause = function() {
        this.pausePromise = new Promise((resolve, reject) => {
            this.paused = true;
            this.notifyStateChange("paused");
            this.resolvePausePromise = resolve;
        });
        return Promise.resolve();
    };


    MacroPlayer.prototype.unPause = function() {
        this.paused = false;
        this.notifyStateChange("playing");
        typeof this.resolvePausePromise == "function"
            && this.resolvePausePromise();
        return Promise.resolve();
    };


    MacroPlayer.prototype.isPaused = function() {
        return this.paused;
    };

    
    MacroPlayer.prototype.profiler = {
        
        make_str: function(x) {
            var prepend = function(str, num) {
                str = str.toString(); 
                var x = StrUtils.toInteger(str), y = StrUtils.toInteger(num);
                if (isNaN(x) || isNaN(y))
                    return;
                while (str.length < num)
                    str = '0'+str;
                return str;
            };
            var str = prepend(x.getHours(), 2)+":"+
                prepend(x.getMinutes(), 2)+":"+
                prepend(x.getSeconds(), 2)+"."+
                prepend(x.getMilliseconds(), 3);
            return str;
        },

        init: function() {
            this.profiler_data = new Array();
            this.macroStartTime = new Date();
        },

        start: function(action) {
            if (!this.enabled)
                return;
            if (this.currentAction !== action) { 
                this.currentAction = action;
                this.startTime = new Date();
            }
        },

        
        end: function(err_text, err_code, mplayer) {
            if (!this.enabled || !this.startTime)
                return;
            var now = new Date();
            var elapsedTime = (now.getTime()-this.startTime.getTime())/1000;

            
            var data = {
                Line: this.currentAction.line+mplayer.linenumber_delta,
                StartTime: this.make_str(this.startTime),
                EndTime: this.make_str(now),
                ElapsedSeconds: elapsedTime.toFixed(3),
                StatusCode: err_code,
                StatusText: err_text,
                type: mplayer.ignoreErrors ? "errorignoreyes" : "errorignoreno"
            };
            
            
            if (this.currentAction.name == "tag") {
                var threshold = (mplayer.timeout_tag > 0) ?
                    mplayer.timeout_tag : mplayer.timeout/10;
                
                data.timeout_threshold =
                    ((elapsedTime/threshold)*100).toFixed();
            } else if (this.currentAction.name == "url") {
                
                data.timeout_threshold =
                    ((elapsedTime/mplayer.timeout)*100).toFixed();
            }
            
            this.profiler_data.push(data);

            
            delete this.currentAction;
            delete this.startTime;
        },

        getResultingXMLFragment: function(mplayer) {
            if (!this.enabled)
                return "";
            var macroEndTime = new Date();
            var source = mplayer.source.trim().split("\n");
            var doc = document.implementation.createDocument("", "Profile", null);
            var macro = doc.createElement("Macro");
            var name = doc.createElement("Name");
            name.textContent = mplayer.currentMacro.name;
            macro.appendChild(name);

            var lastStartTime = null; 

            
            var j = mplayer.linenumber_delta == 0 ? 0 :
                -mplayer.linenumber_delta;
            for (var i = 0; i < source.length; i++) {
                if (j < this.profiler_data.length &&
                    this.profiler_data[j].Line == i+1+mplayer.linenumber_delta) {
                    var command = doc.createElement("Command");
                    var string = doc.createElement("String");
                    
                    string.textContent = source[i].trim();
                    command.appendChild(string);
                    var x = this.profiler_data[j];
                    for (var y in x) {
                        if (y != "type" && y != "timeout_threshold") {
                            var z = doc.createElement(y);
                            z.textContent = x[y];
                            command.appendChild(z);
                        }
                    }
                    
                    command.setAttribute("type", x.type);
                    
                    if (x.timeout_threshold) {
                        var tt = command.setAttribute("timeout_threshold", 
						      x.timeout_threshold);
                    }
                    lastStartTime = x.StartTime;
                    j++;
                    
                    macro.appendChild(command);
                }
            }

            
            var start = doc.createElement("Start"); 
            start.textContent = this.make_str(this.macroStartTime);
            var end = doc.createElement("End"); 
            end.textContent = this.make_str(macroEndTime);
            var elapsed = doc.createElement("ElapsedSeconds"); 
            var duration = (macroEndTime.getTime()-
                            this.macroStartTime.getTime())/1000;
            elapsed.textContent = duration.toFixed(3);
            var status = doc.createElement("Status"); 
            var code = doc.createElement("Code");
            code.textContent = mplayer.errorCode;
            var text = doc.createElement("Text");
            text.textContent = mplayer.errorMessage;
            
            status.appendChild(code);
            status.appendChild(text);
            macro.appendChild(start);
            macro.appendChild(end);
            macro.appendChild(elapsed);
            macro.appendChild(status);
            
            doc.documentElement.appendChild(macro);
            var s = new XMLSerializer();
            var result = s.serializeToString(doc);

            return result.replace(/^[.\n\r]*<Profile>\s*/, "").replace(/\s*<\/Profile>/, "");
        }
    };

    MacroPlayer.prototype.saveProfilerData = function() {
        let xml_frag = this.profiler.getResultingXMLFragment(this);
        let file_path = null;
        if (this.profiler.file) { 
            if (FIO.isFullPath(this.profiler.file)) {
                file_path = this.profiler.file;
            } else {
                let leafname = /\.xml$/i.test(this.profiler.file)?
                    this.profiler.file : this.profiler.file+".xml";
                file_path = OS.Path.join(Prefs.defaultDownloadPath, 
                                         ...OS.Path.split(leafname).components);
            }
        } else {
            file_path = OS.Path.join(Prefs.defaultDownloadPath,
                                     "Firefox_Profiler_"+
                                     StrUtils.formatDate("yyyy-mm-dd")+".xml");
        }

        return OS.File.exists(file_path).then(exists => {
            if (exists) {
                return FIO.readTextFile(file_path).then(x => {
                    x = x.replace(/\s*<\/Profile>\s*$/, "\n"+
                                  xml_frag+"</Profile>");
                    return FIO.writeTextFile(file_path, x);
                });
            } else {
                let x = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
                    "<?xml-stylesheet type='text/xsl' href='Profiler.xsl'?>\n"+
                    "<Profile>\n"+
                    "<!--Profiled with iMacros for Firefox "+
                    Prefs.version+" on "+(new Date())+"-->";
                x += xml_frag;
                x += "</Profile>";
                return FIO.writeTextFile(file_path, x);
            }
        });
    };

    
    
    MacroPlayer.prototype.beforeEachRun = function() {
        
        this.stopwatchMap = new Map();
        this.stopwatchResults = new Array();
        this.totalRuntime = 0;
        this.stopwatchFile = null;  
        this.stopwatchFolder = null; 
        this.shouldWriteStopwatchFile = true; 
        
        this.lastWatchValue = 0;
        this.lastPerformance = "";
        this.lastPerformanceArray = new Array();

	this.currentFrame = {number: 0};
        
        this.globalTimer.start();

        
        this.popupAllowed = null;

        
        this.loadAfterStop = true;

        
        this.proxySettings = null;

        
        this.useragent = null;

        
        this.clearExtractData();

        
        
        

        
        this.writeProfilerData = Prefs.profilerEnabled;
        this.profiler.file = null;
        
        this.profiler.init();
        this.profiler.enabled = this.profiler.si_enabled ||
            Prefs.profilerEnabled;
        
        this.imageX = this.imageY = -1;
    };


    MacroPlayer.prototype.afterEachRun = function() {
        this.cleanUpPopupAllowed();
        this.cleanUpProxySettings();
        this.cleanUpUserAgent();
        
        
        RequestWatcher.disableImageFilter();
        
        return this.saveStopwatchResults().then( () => {
            
            if (this.writeProfilerData) {
                this.writeProfilerData = false;
                return this.saveProfilerData();
            }
        });
    };

    
    MacroPlayer.prototype.reset = function() {
        
        this.shouldStopOnError = false;

        
        this.globalTimer.init();
        
        this.ignoreErrors = false;
        this.stopPromise = new Promise((resolve, reject) => {
            this.rejectStopPromise = reject;
            this.resolveStopPromise = resolve;
        });
        this.playing = false;
        this.paused = false;
        this.inEventsCommand = false;
        
        this.currentLoop = 1;
        this.firstLoop = true;
        
        this.playingAgain = false;
        this.networkError = false;
        this.networkErrorProxyConnection = false;
        this.networkErrorHTTPCode = 0;
        this.downloadFilename = null;
        this.downloadFolder = null;
        this.downloadCheckAlg = "";
        this.downloadChecksum = "";
        this.downloadedFilename = "undefined"; 
        this.downloadedSize = 0;
        
        this.extractData = "";
        this.shouldPopupExtract = true;
        
        this.dataSource = new Array();
        this.dataSourceColumns = 0;
        this.dataSourceLine = 0;
        this.dataSourceFile = "";
        this.dataSourceDelimiter = ",";
        this.dataSourceFolder = Prefs.defaultDataPath;
        
        this.timeout = Prefs.pageTimeout;
        this.timeout_tag = Math.round(this.timeout/10);
        this.delay = Prefs.replayingDelay;
        
        this.shouldFilterImages = true;
        
        this.shouldDownloadPDF = false;
        
        this.startTabIndex = gBrowser.mTabContainer.selectedIndex;
        
        this.errorCode = 1;
        this.errorMessage = "OK";
        
        this.singleStepMode = false;
        
        this.cycledReplay = false;
        
        this.linenumber_delta = 0;
        
        
        iMacros.panel.mboxResetError();

        
        
        let popup = null;
        try {
            popup = Prefs.prefBranch.getCharPref("popupAllowed");
        } catch (e) {}
        if (popup) {
            Services.perms.remove(popup, "popup");
            Prefs.prefBranch.clearUserPref("popupAllowed");
        }


        
        
        let branch = Services.prefs.getBranch("browser.link.");
        let open_newwindow =  branch.getIntPref("open_newwindow");
        let open_newwindow_restriction = branch.
            getIntPref("open_newwindow.restriction");
        Storage.set("open_newwindow", open_newwindow);
        if (open_newwindow != 3) {
            branch.setIntPref("open_newwindow", 3);
        }
        Storage.set("open_newwindow_restriction",
                    open_newwindow_restriction);
        if (open_newwindow_restriction != 0) {
            branch.setIntPref("open_newwindow.restriction", 0);
        }

        
        this.warnCommands = null;
    };
    

    
    MacroPlayer.prototype.getExtractData = function () {
        return this.extractData;
    };

    MacroPlayer.prototype.addExtractData = function(str) {
        if ( this.extractData.length ) {
            this.extractData += "[EXTRACT]"+str;
        } else {
            this.extractData = str;
        }
    };

    MacroPlayer.prototype.clearExtractData = function() {
        this.extractData = "";
    };


    
    MacroPlayer.prototype.showAndAddExtractData = function(str) {
        this.addExtractData(str);
        if (!this.shouldPopupExtract ||
            iMacros.in_iimPlay ||
            this.client_id ||
            this.cycledReplay
           )
            return;
        let param = {extractData: str};
        window.openDialog('chrome://imacros/content/extract.xul', '',
                          'modal,centerscreen', param);
    };



    
    MacroPlayer.prototype.loadDataSource = function(filename) {
        let file_path = FIO.isFullPath(filename) ? filename :
            OS.Path.join(this.dataSourceFolder, ...OS.Path.split(filename).components);

        return OS.File.exists(file_path).then(exists => {
            if (!exists)
                throw new RuntimeError("Data source file does not exist", 930);
            this.dataSourceFile = file_path;

            return FIO.readTextFile(file_path).then(data => {
                if (!/\r?\n$/.test(data))
                    data += "\n";     
                this.dataSource = new Array();
                
                
                const ws = '[ \t\v]';   
                
                const delim = this.dataSourceDelimiter;
                const field = ws+'*("(?:[^\"]+|"")*"|[^'+delim+'\\n\\r]*)'+ws+
                      '*('+delim+'|\\r?\\n|\\r)';
                let re = new RegExp(field, "g"), m, vals = new Array();
                while (m = re.exec(data)) {
                    let value = m[1], t;
                    if (t = value.match(/^\"((?:[\r\n]|.)*)\"$/))
                        value = t[1];   
                    value = value.replace(/\"{2}/g, '"'); 

                    
                    
                    
                    
                    if (t = value.match(/^\"((?:[\r\n]|.)*)\"$/))
                        value = '"\\"'+t[1]+'\\""';
                    vals.push(value);

                    if (m[2] != delim) {
                        this.dataSource.push(vals.slice(0));
                        vals = new Array();
                    }
                }

                if (!this.dataSource.length)
                    throw new RuntimeError("Can not parse datasource file "+
                                           filename, 952);
            });
        });
    };


    MacroPlayer.prototype.getColumnData = function (col) {
        let line = this.dataSourceLine || this.currentLoop;
        if (!line) 
            line = 1;
        let max_columns = this.dataSourceColumns ||
            this.dataSource[line-1].length;
        if (col > max_columns)
            throw new RuntimeError("Column number "+col+
                                   " greater than total number"+
                                   " of columns "+max_columns, 953);
        
        return this.dataSource[line-1][col-1];
    };


    MacroPlayer.prototype.evalString = function(s) {
        
        function MacroError(msg, num) {
            this.message = msg;
            if (typeof num != "undefined")
                this.errnum = num;
            this.name = "MacroError";
        }

        MacroError.prototype = Error.prototype;
        
        let str = s ? StrUtils.unwrap(s) : "";
        let err = function(txt) {
            throw new MacroError(txt, -1340);
        };

        
	let principal = this.currentBrowser.contentPrincipal;
        let sandbox = Components.utils.Sandbox(principal);

        sandbox.importFunction(err, "MacroError")
        let result = Components.utils.evalInSandbox(str, sandbox);
        return (typeof result == "undefined" ? "" : result).toString();
    };


    
    
    
    
    MacroPlayer.prototype.expandVariables = function(param) {
        
        param = param.replace(/#novar#\{\{/ig, "#NOVAR#{");
        
        
        var handleVariable = (match_str, var_name) => {
            var t = null;
            if ( t = var_name.match(/^!var([0-9])$/i) ) {
                return this.vars[StrUtils.toInteger(t[1])];
            } else if ( t = var_name.match(/^!extract$/i) ) {
                return this.getExtractData();
            } else if ( t = var_name.match(/^!errorignore$/i) ) {
                return this.ignoreErrors ? "YES" : "NO";
            } else if ( t = var_name.match(/^!encryption$/i) ) {
                switch(pm.encryptionType) {
                case pm.TYPE_NONE:
                    return "NO"; break;
                case pm.TYPE_STORED:
                    return "STOREDKEY"; break;
                case pm.TYPE_TEMP:
                    return "TMPKEY"; break; 
                }
            } else if ( t = var_name.match(/^!loop$/i) ) {
                return this.currentLoop;
            } else if ( t = var_name.match(/^!urlcurrent$/i) ) {
                return this.currentBrowser.currentURI.spec;
            } else if ( t = var_name.match(/^!now:(\S+)$/i) ) {
                return StrUtils.formatDate(t[1]).toString();
            } else if ( t = var_name.match(/^!col(\d+)$/i) ) {
                return this.getColumnData(StrUtils.toInteger(t[1]));
            } else if ( t = var_name.match(/^!datasource_line$/i) ) {
                return this.dataSourceLine || this.currentLoop;
            } else if ( t = var_name.match(/^!datasource_columns$/i) ) {
                return this.dataSourceColumns;
            } else if ( t = var_name.match(/^!datasource_delimiter$/i) ) {
                return this.dataSourceDelimiter;
            } else if ( t = var_name.match(/^!datasource$/i) ) {
                return this.dataSourceFile;
            } else if ( t = var_name.match(/^!folder_datasource$/i) ) {
                return this.dataSourceFolder.path;
            } else if ( t = var_name.match(/^!downloaded_file_name$/i) ) {
                return this.downloadedFilename;
            } else if ( t = var_name.match(/^!downloaded_size$/i) ) {
                return this.downloadedSize;
            } else if ( t = var_name.match(/^!stopwatchtime$/i) ) {
                
                var value = this.lastWatchValue.toFixed(3).toString();
                return value;
            } else if ( t = var_name.match(/^!clipboard$/i) ) {
                return Clipboard.getString() || "";
            } else if ( t = var_name.match(/^!timeout(?:_page)?$/i) ) {
                return this.timeout;
            } else if ( t = var_name.match(/^!timeout_(?:tag|step)?$/i) ) {
                return this.timeout_tag;
            } else if ( t = var_name.match(/^!timeout_macro$/i) ) {
                return this.globalTimer.macroTimeoutValue || "undefined";
            } else if ( t = var_name.match(/^!imagex$/i) ) {
                return this.imageX;
            } else if ( t = var_name.match(/^!imagey$/i) ) {
                return this.imageY;
            }else if ( t = var_name.match(/^!singlestep$/i) ) {
                return this.singleStepMode.toString();
            } else if ( t = var_name.match(/^!replayspeed$/i) ) {
                if  (this.delay <= 100 ) {
                    return "FAST";
                } else if (this.delay <= 1000) {
                    return "MEDIUM";
                } else {
                    return "SLOW";
                }
            } else {                
                var value = "__undefined__";
                if (this.userVars.has(var_name.toLowerCase()))
                    value = this.userVars.get(var_name.toLowerCase());
                return value;
            }
        };


        
        var eval_re = new RegExp("^eval\\s*\\((.*)\\)$", "i");
        var match = null;
        if (match = eval_re.exec(param)) {
            var escape = function (s) {
                var x = s.toString();
                return x.replace(/"/g, "\\\\\"").
                    replace(/'/g, "\\\\\'").
                    replace(/\n/g, "\\\\n").
                    replace(/\r/g, "\\\\r");
            };
            var js_str = match[1].replace(/\{\{(\S+?)\}\}/g, function(m, s) {
                return escape(handleVariable(m, s))
            });
            
            js_str = js_str.replace(/#novar#\{(?=[^\{])/ig, "{{");
            param = this.evalString(js_str);
        } else {
            param = param.replace(/\{\{(\S+?)\}\}/g, handleVariable);
            
            param = param.replace(/#novar#\{(?=[^\{])/ig, "{{");
        }

        return param;
    };

    return new MacroPlayer();
})();
